刷题日记 | LeetCode刷题总结(C++)

©一颗斯特拉
【注】
1..参考书:
①C++谭浩强
②C++数据结构与算法 (第4版)


66. 加一(4月24日)

01 方法

思路

从末位开始往前遍历,如果到某位加一时该位是9,则将其变为0,再把它前面一位数加一。

02 反思&总结

这道题用C++很容易,因为C++里有自带的一些向量函数。但是用C做不那么容易了。这里学习下大神的解法:

int *plusOne(int *digits, int digitsSize, int *returnSize) {

    for (int i = digitsSize - 1; i >= 0; --i) {
        if (digits[i] == 9) {
            digits[i] = 0;
        } else {
            digits[i]++;
            *returnSize = digitsSize;
            return digits;
        }
    }
    int *result = (int *) malloc(sizeof(int) * (digitsSize + 1));
    memset(result, 0, (digitsSize + 1) * sizeof(int));
    result[0] = 1;
    *returnSize = digitsSize + 1;
    return result;
}

主要讲讲上述解答中涉及到的两个关于数组的函数:
1.malloc()
C 库函数 void *malloc(size_t size) 分配所需的内存空间,并返回一个指向它的指针。
声明:void *malloc(size_t size)
参数:size -- 内存块的大小,以字节为单位。
返回值:该函数返回一个指针 ,指向已分配大小的内存。如果请求失败,则返回 NULL。
2.memset()
C 库函数 void *memset(void *str, int c, size_t n) 复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符。
声明:void *memset(void *str, int c, size_t n)
参数:

  • str -- 指向要填充的内存块。
  • c -- 要被设置的值。该值以 int 形式传递,但是函数在填充内存块时是使用该值的无符号字符形式。
  • n -- 要被设置为该值的字节数。
    返回值:该值返回一个指向存储区 str 的指针。

70. 爬楼梯(4月23号)

反思&总结

class Solution {
public:
    int climbStairs(int n) {
        if(n==1||n==2){
            return n;
        }
        else {
            return climbStairs(n-1)+climbStairs(n-2);
        }
    }
};

以上使用递归做的,超出了时间限制。那么就用动态规划做了,空间复杂度增大,之前做过一道题也遇到了这个问题。

class Solution {
public:
    int climbStairs(int n) {
        if(n<3){
            return n;
        }
        int dp[n];
        dp[0] = 1;
        dp[1] = 2;
        for(int i=2;i

如上,写出动态规划的决策集,用这种方法做就不会超出时间限制啦。


168. Excel表列名称(4月26日)

先弄清楚Excel表列名称的命名规则。
Excel表的列是怎么命名的

知道了命名规则后,我们再想怎么才能快速地把那一列的名称pick出来。
鹅,看了很久参考解答发现以我目前的水平是做不出来的,先放弃。


892. 三维形体的表面积(3月25日)

01 方法[1]

【分步累加】
思路

每个值v = grid[i][j] 表示 v个正方体叠放在对应单元格(i, j)上。单独计算每个单元格(i,j)上所有正方体所贡献的表面积值,再将所有单元格上的表面积值加起来就能得解。计算表面积时,分两种情况:
①顶面和底面的表面积:如果v>0,即代表那个单元格有正方体,那么顶面和顶面各贡献了1的表面积,总共贡献了2的表面积。
②四个侧面的表面积:只有相邻位置的高度小于v时,对应的那个侧面才会贡献表面积,且贡献的数量为v-nv,其中nv是相邻位置的高度,可以写成max(v-nv,0)

算法

对于每个 v = grid[r][c] > 0,计算 ans += 2。对于 grid[r][c] 四个方向的每个相邻值nv 还要加上 max(v - nv, 0)

复杂度分析
  • 时间复杂度:,其中 N是grid中的行和列的数目。
  • 空间复杂度:。

02 知识点

1.vector

一、介绍

  • 定义(是什么):向量vector是一种对象实体, 能够容纳许多其他类型相同的元素, 因此又被称为容器。 与string相同, vector同属于STL(Standard Template Library, 标准模板库)中的一种自定义的数据类型, 可以广义上认为是数组的增强版。
  • 优点(为什么用):vector容器与数组相比其优点在于它能够根据需要随时自动调整自身的大小以便容下所要放入的元素。此外, vector也提供了许多的方法来对自身进行操作。
  • 用法(怎么用):在使用它时, 需要包含头文件#include

二、向量的基本操作
1.a.size():获取向量中的元素个数

三、二维向量
与数组相同, 向量也可以增加维数, 例如声明一个m*n大小的二维向量方式可以像如下形式:

vector< vector > b(10, vector(5)); //这里,两个“>”间的空格是不可少的

在这里, 实际上创建的是一个向量中元素为向量的向量。同样可以根据一维向量的相关特性对二维向量进行操作。 除此之外, 还可以直接使用数组来初始化向量。

2.引用

一、定义(是什么):对变量起另外一个名字 (外号),这个名字称为该变量的引用。

<类型> &<引用变量名> = <原变量名>;其中原变量名必须是一个已定义过的变量

//注意:①引用并没有重新在内存中开辟单元,只是引用原变量的单元,两者在内存中占用同一地址,即同一地址两个名字。②当&a的前面有类型符时 (如int &a),它必然是对引用的声明;如果前面无类型符(如cout<<&a), 则是取变量的地址。

二、用法(怎么用):引用的用途主要是用来作函数的参数或函数的返回值。 引用作函数的形参,实际上是在被调函数中对实参变量进行操作。
//注意:引用作为形参,实参是变量而不是地址,这与指针变量作形参不一样。


1089. 复写零(4月10日)

01 知识点

1.vector的删除操作

(1)popback
向量容器的成员函数pop_back(),函数可以删除最后一个元素,

vector vec; vec.popback;

(2)函数erase()可以删除由一个iterator指出的元素: vector::iterator ito; vec.erase(iterator);
同样也可以删除一个指定范围内的元素: vec.erase(vec.begin(),vec.end());

(3)还可以采用通用算法remove()来删除vector容器中的元素。不同的是,采用remove一般情况下不会改变容器的大小,而pop_back()与erase()成员函数会改变容器的大小。

2.vector的增添操作

insert() 函数有以下三种用法:

iterator insert( iterator loc, const TYPE &val ); //在指定位置loc前插入值为val的元素,返回指向这个元素的迭代器,

void insert( iterator loc, size_type num, const TYPE &val ); //在指定位置loc前插入num个值为val的元素

void insert( iterator loc, input_iterator start, input_iterator end ); //在指定位置loc前插入区间[start, end)的所有元素

3.auto的用法

下面列举auto关键字的正确用法。

用于代替冗长复杂、变量使用范围专一的变量声明。

想象一下在没有auto的时候,我们操作标准库时经常需要这样:

#include
#include
int main()
{
    std::vector vs;
    for (std::vector::iterator i = vs.begin(); i != vs.end(); i++)
    {
        //...
    }
}

这样看代码写代码实在烦得很。有人可能会说为何不直接使用using namespace std,这样代码可以短一点。实际上这不是该建议的方法(C++Primer对此有相关叙述)。使用auto能简化代码:

#include
#include
int main()
{
    std::vector vs;
    for (auto i = vs.begin(); i != vs.end(); i++)
    {
        //..
    }
}

for循环中的i将在编译时自动推导其类型,而不用我们显式去定义那长长的一串。

4.为什么尽量不要用using namespace std

1180. 统计只含单一字母的子串(4月14号)

01方法

思路

对于一个长度为n的只含单一字母的字符串,其子串的个数为。


1365. 有多少小于当前数字的数字(3月26日)

01 方法

【暴力法】
思路

枚举数组里的每个数字,遍历数组统计有多少个数字比当前小。

算法

对于第个数字,计算。其中我们约定括号里会返回一个数字,为真返回1,否则返回0。

复杂度分析
  • 时间复杂度:。枚举数组中的元素为,遍历也为,其中。
  • 空间复杂度:。这里不需要开辟新的空间。
【桶计数】[2]
思路

注意到数字的值域范围为 ,所以可以考虑建立一个频次数组 ,表示数字 出现的次数,那么对于数字 而言,它的答案就是,即小于它的数字出现个数之和,直接算需要遍历。仍需要线性的时间去计算,但我们注意到这个答案是一个前缀和,所以我们可以再对算答案求前缀和,那么对于数字而言,它的答案就是,这样就把时间复杂度而言,它的答案就是降到了,

算法

遍历数组元素,更新数组,然后对其求前缀和,最后遍历数组元素,对于相应的数字在时间得到答案即可。

复杂度分析
  • 时间复杂度:,其中为值域的大小,。
  • 空间复杂度:,需要开一个值域大小的数组。

比较:
【暴力法】| 1 小时前 | 通过 | 100 ms | 7 MB | Cpp |
【桶计数】| 几秒前 | 通过 | 4 ms | 7 MB | Cpp |
桶计数的执行用时明显减少,内存消耗相同。

02 知识点

1.前缀和[3]

一、定义
一维前缀和:前缀和顾名思义就是前面数的总和。这个优化主要是用来在时间内求出一个序列中,的和。具体原理十分简单:用表示,其中,则即等于。

二、应用问题
核心就两个字:降维。
面对许多高维问题,往往前缀和是最先想到的降维方法。
这样在降维的基础上,许多更进一步的优化才能实现。


1323. 6 和 9 组成的最大数字(4月2日)

02 知识点

1.to_string函数

一、函数原型:
string to_string (int val);
string to_string (long val);
string to_string (long long val);
string to_string (unsigned val);
string to_string (unsigned long val);
string to_string (unsigned long long val);
string to_string (float val);
string to_string (double val);
string to_string (long double val);

二、功能:将数值转化为字符串。返回对应的字符串。

2.stoi函数

一、原型:stoi(string)

二、功能:将字符串转化为整型变量。stoi函数会做范围检查,所需转换的数字如果超出int范围,即超出[-2147483648,2147483648],会出现Runtime Error。所以当题目输入字符串长度大于等于10位时,一定要注意!

3.C++中的字符串

C++ 提供了以下两种类型的字符串表示形式:

  • C 风格字符串
  • C++ 引入的 string 类类型

C++ 标准库提供了 string 类类型,支持C 风格字符串的所有的操作,另外还增加了其他更多的功能。


1351. 统计有序矩阵中的负数(4月26号)

官方解答中有二分法、分治法,都是超过我目前能力之外的算法。自己写了个时间复杂度也不高的解答。

//矩阵中的元素无论是按行还是按列,都以非递增顺序排列。 怎么运用这个条件更好地简化
class Solution {
public:
    int countNegatives(vector>& grid) {
        int num=0;
        int m,n;
        m=grid.size();//行数
        n=grid[0].size();//列数
        for(int i=0;i
执行结果

1370. 上升下降字符串(3月25日)

01 方法[4]

【桶计数】

思路

桶计数的思路是建立一个长度为 26 的数组表示 26 个桶,按顺序存放26个英文字母。实现本题目标,需要分两步走:
①先用的时间扫描一遍字符串(其中代表字符串的长度),统计每个字母出现的次数。
②然后不停地扫描这里的这个桶序列,先从小到大扫,再从大到小扫,每次发现一个桶当中计数值不为 0 的时候,就把这个桶对应的字母添加到结果字符串的最后方,然后对计数值减一。

算法

①建立一个长度为 26 的数组h[],作为用来计数的「桶」。
②引入内联的返回布尔型值得函数haveChar(),它的功能是在每次循环开始执行之前判断是否还有未使用的字符。
③引入函数appendChar(int i),它的功能是检测i位置的桶是否计数值为 0,如果不为 0 则修改目标串和计数值。

复杂度分析
  • 时间复杂度:
  • 空间复杂度:这里使用了长度为 26 的数组作为辅助空间,故渐进空间复杂度为

02 知识点

1.内联函数

一、定义(是什么):将被调函数体的代码直接插到调用处的函数。

二、优点(为什么):内联函数的实质是用存储空间(使用更多的存储空间)来换取时间(减少执行时间)。

三、用法(怎么用): 内联函数的定义方法是,在函数定义时,在函数的类型前增加修饰词inline

2.auto[5]

auto是c++程序设计语言的关键字。用于两种情况:
①声明变量时根据初始化表达式自动推断该变量的类型(不建议使用)
例如,

auto f = 3.14;  //double
auto s("hello");  //const char*
auto z = new auto(9);  //int *
auto x1 = 5, x2 = 5.0, x3 = 'r';   //错误,必须是初始化为同一类型

②声明函数时函数返回值的占位符
auto关键字更适用于类型冗长复杂、变量使用范围专一时,使程序更清晰易读。如:

std::vector vect; 
 for(auto it = vect.begin(); it != vect.end(); ++it)
 {  //it的类型是std::vector::iterator
    std::cin >> *it;
  }

或者保存lambda表达式类型的变量声明:

  auto ptr = [](double x){return x*x;};//类型为std::function函数对象
3.Lambda表达式

一、定义(是什么):Lambda 表达式(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。Lambda表达式可以表示闭包(注意和数学传统意义上的不同)。

二、用法(怎么用):

[capture list] (parameter list) -> return type { function body }

其中除了“[ ]”(其中捕获列表可以为空)和“复合语句”(相当于具名函数定义的函数体),其它都是可选的。它的类型是单一的具有成员operator()的非联合的类类型,称为闭包类型(closure type)。
C++中,一个lambda表达式表示一个可调用的代码单元。我们可以将其理解为一个未命名的内联函数。它与普通函数不同的是,lambda必须使用尾置返回来指定返回类型。
由于Lambda的类型是单一的,不能通过类型名来显式声明对应的对象,但可以利用auto关键字和类型推导:

auto f=[](int a,int b){return a>b;};

因为参数类型和函数模板参数一样可以被推导而无需和具体参数类型耦合,有利于重构代码;和使用auto声明变量的作用类似,它也允许避免书写过于复杂的参数类型。特别地,不需要显式指出参数类型时使用高阶函数变得更加容易。

4.STL(Standard Template Library)[6]

一、STL是什么
STL标准模板库,是一个具有工业强度的,高效的C++程序库。它被容纳于C++标准程序库(C++ Standard Library)中,是ANSI/ISO C++标准中最新的也是极具革命性的一部分。该库包含了诸多在计算机科学领域里所常用的基本数据结构和基本算法。为广大C++程序员们提供了一个可扩展的应用框架,高度体现了软件的可复用性。
STL的一个重要特点是数据结构和算法的分离。尽管这是个简单的概念,但这种分离确实使得STL变得非常通用。例如,由于STL的sort()函数是完全通用的,你可以用它来操作几乎任何数据集合,包括链表,容器和数组。
STL另一个重要特性是它不是面向对象的。为了具有足够通用性,STL主要依赖于模板而不是封装,继承和虚函数(多态性)——OOP的三个要素。你在STL中找不到任何明显的类继承关系。这好像是一种倒退,但这正好是使得STL的组件具有广泛通用性的底层特征。另外,由于STL是基于模板,内联函数的使用使得生成的代码短小高效。
从逻辑层次来看,在STL中体现了泛型化程序设计的思想,引入了诸多新的名词,比如像需求(requirements),概念(concept),模型(model),容器(container),算法(algorithmn),迭代子(iterator)等。与OOP(object-oriented programming)中的多态(polymorphism)一样,泛型也是一种软件的复用技术。
从实现层次看,整个STL是以一种类型参数化的方式实现的,这种方式基于一个在早先C++标准中没有出现的语言特性--模板(template)。

二、常见用法
1.push_back:push_back是编程语言里面的一个函数名。如c++中的vector头文件里面就有这个push_back函数,在vector类中作用为在vector尾部加入一个数据。string中也有这个函数,作用是字符串之后插入一个字符。

void push_back(value _type _Ch);
_Ch --> The character to be added to the end of the string.

string a="123";

a.push_back('3'); //在字符串末尾添加一个字符,结果为 a="1233";

a.pop_back(); //在字符串末尾删除一个字符,结果为 a="12";
5.bool函数[7]

一、定义:BOOL是布尔型变量,也就是逻辑型变量的定义符,类似于float、double等,只不过float定义浮点型,double定义双精度浮点型。在objective-c中提供了相似的类型BOOL,它具有YES值和NO值。布尔型变量的值只有真(true)和假(false),可用于逻辑表达式,也就是“或”“与”“非”之类的逻辑运算和大于小于之类的关系运算,逻辑表达式运算结果为真或为假。(百科)

二、优点:C++中如果值非0就为True,为0就是False。比如:bool b;b=(1>2) //此时b为false。b=(2>1) //此时b为true
比如讲你在写数据结构的时候,有时候需要判断一下链表是不是为空,这时候需要用到bool函数,再者,你看到bool就知道这个函数返回值只是用于判断真假。
比如你写一个比较两个字符是否相等的函数,如果不相等就返回真,否则返回··假,你可以写:

int function(char a,char b)
{
return a-b;
}

但是bool函数返回的只有true和false。而int会返回各种数字,但是你关心的不是数字的多少,而是这个数字为不为0.所以这种情况用bool会更加简洁,规范,你看到bool就知道这是一个判断真假函数,但是你看到是int型呢?你可能会以为返回的数字有用,又要重新看看程序。
当你写一个程序,要调用100多个自定义函数,其中又有几十个判断真假的函数时,你全用int结果可想而知!

6.for (auto &c : s)

这是特殊的for循环语句。for循环中的每个迭代初始化一个新的引用。
如果要更改字符串中的字符值,我们必须将循环变量定义为引用类型。请记住,引用只是给定对象的另一个名称。当我们使用引用作为我们的控制变量时,该变量依次绑定到序列中的每个元素。使用引用,我们可以更改引用所绑定的字符。

for (auto &c : s)   
c = toupper(c); 

相当于:

for (auto it = s.begin(); it != s.end(); ++it)
{
    auto &c = *it;
    c = toupper(c);
}

本题解中,

for(const auto &c:s) ++h[c-'a'];

是利用编码相减的偏移量,做了一个hash表随时存取。


  1. 【参考资料】
    作者:LeetCode-Solution
    链接:https://leetcode-cn.com/problems/surface-area-of-3d-shapes/solution/san-wei-xing-ti-de-biao-mian-ji-by-leetcode-soluti/
    来源:力扣(LeetCode)
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 ↩

  2. 【参考资料】
    作者:LeetCode-Solution
    链接:https://leetcode-cn.com/problems/how-many-numbers-are-smaller-than-the-current-number/solution/you-duo-shao-xiao-yu-dang-qian-shu-zi-de-shu-zi--2/
    来源:力扣(LeetCode)
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 ↩

  3. 【参考资料】
    版权声明:本文为CSDN博主「K_rew」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/K_rew/article/details/50527287 ↩

  4. 【参考资料】
    作者:LeetCode-Solution
    链接:https://leetcode-cn.com/problems/increasing-decreasing-string/solution/shang-sheng-xia-jiang-zi-fu-chuan-by-leetcode-solu/
    来源:力扣(LeetCode)
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 ↩

  5. 【参考资料】
    版权声明:本文为CSDN博主「小拳头」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/xiaoquantouer/article/details/51647865 ↩

  6. 【参考资料】
    版权声明:本文为CSDN博主「HUST_Miao」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/u010183728/article/details/81913729 ↩

  7. 【参考资料】
    版权声明:本文为CSDN博主「Single-minded T」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/qq_40638006/article/details/80736559 ↩

你可能感兴趣的:(刷题日记 | LeetCode刷题总结(C++))