7.结构体、类、指针与引用
21. 斐波那契数列
输入一个整数 n ,求斐波那契数列的第 n 项。
假定从 0 开始,第 0 项为 0。
数据范围 0≤n≤39 样例 输入整数 n=5
返回 5
class Solution {
public:
int Fibonacci(int n) {
int a = 0, b = 1, c;
while (n -- ){
c = a + b;
a = b, b = c;
}
return a;
}
};
// if(n == 0) return 0;
// if(n == 1) return 1;
// else return Fibonacci(n-1) + Fibonacci(n-2);
16. 替换空格
请实现一个函数,把字符串中的每个空格替换成"%20"。
数据范围 0≤ 输入字符串的长度 ≤1000。 注意输出字符串的长度可能大于 1000。
样例 输入:"We are happy."
输出:"We%20are%20happy."
STL
class Solution {
public:
string replaceSpaces(string &str) {
int t;//若找到' '返回第一个出现的下标
while((t = str.find(' ')) != string::npos){ //npos记:参数下标n的position
str.replace(t, 1, "%20");//(替换起始位置, 替换原串长度, 替换的字符串)
}
return str;
}
};
(线性扫描) O(n)
这个题在C++里比较好做,我们可以从前往后枚举原字符串: 如果遇到空格,则在string类型的答案中添加 "%20"; 如果遇到其他字符,则直接将它添加在答案中;
但在C语言中,我们没有string这种好用的模板,需要自己malloc出char数组来存储答案。 此时我们就需要分成三步来做: ①遍历一遍原字符串,计算出答案的最终长度; ②malloc出该长度的char数组; ③再遍历一遍原字符串,计算出最终的答案数组; 时间复杂度分析 原字符串只会被遍历常数次,所以总时间复杂度是 O(n)。
class Solution {
public:
string replaceSpaces(string &str) {
string res;
for (auto x : str)
if (x == ' ')
res += "%20";
else
res += x;
return res;
}
};
(双指针扫描) O(n)
在部分编程语言中,我们可以动态地将原数组长度扩大,此时我们就可以使用双指针算法,来降低空间的使用:
首先遍历一遍原数组,求出最终答案的长度length; 将原数组resize成length大小; 使用两个指针,指针i指向原字符串的末尾,指针j指向length的位置; 两个指针分别从后往前遍历,如果str[i] == ' ',则指针j的位置上依次填充'0', '2', '%',这样倒着看就是"%20";如果str[i] != ' ',则指针j的位置上填充该字符即可。 由于i之前的字符串,在变换之后,长度一定不小于原字符串,所以遍历过程中一定有i <= j,这样可以保证str[j]不会覆盖还未遍历过的str[i],从而答案是正确的。 (逆序添加02% --> %20)
class Solution {
public:
string replaceSpaces(string &str) {
int len = 0; //计算修改后的长度
for (auto c : str)
if (c == ' ')
len += 3; //%20占三位
else
len ++ ;
int i = str.size() - 1, j = len - 1;
str.resize(len);
while (i >= 0) //逆序添加02% --> %20
{
if (str[i] == ' ')
{
str[j -- ] = '0';
str[j -- ] = '2';
str[j -- ] = '%';
}
else str[j -- ] = str[i];
i -- ;
}
return str;
}
};
78. 左旋转字符串
字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。
请定义一个函数实现字符串左旋转操作的功能。
比如输入字符串"abcdefg"和数字 2,该函数将返回左旋转 2 位得到的结果"cdefgab"。
注意:
数据保证 n 小于等于输入字符串的长度。 数据范围 输入字符串长度 [0,1000]。
样例 输入:"abcdefg" , n=2
输出:"cdefgab"
string构造函数截取子串
string 有个构造函数:传入起点和结束点迭代器,可以构造出迭代器之间的字符串(
左闭右开
)。 分别构造出 s1 为 s[n] ~ 末尾。s2 为 s[0] ~ s[n-1] 返回 s1 + s2 即可.
class Solution {
public:
string leftRotateString(string str, int n) {
string s1(str.begin() + n, str.end());
string s2 (str.begin(), str.begin() + n);
return s1 + s2;
}
};
substr函数
substr 指定长度复制 形式 : s.substr(pos, len) //不指定len时 ,默认s.size() - pos (从pos读到结束)
返回值: string,包含s中从pos开始的len个字符的拷贝
(pos的默认值是0,len的默认值是s.size() - pos,即不加参数会默认拷贝整个s)
异常 : 若pos的值超过了string的大小,则substr函数会抛出一个out_of_range异常; 若pos+n的值超过了string的大小,则substr会调整n的值,只拷贝到string的末尾
class Solution {
public:
string leftRotateString(string str, int n) {
return str.substr(n) + str.substr(0,n);
}
};
87. 把字符串转换成整数
请你写一个函数 StrToInt,实现把字符串转换成整数这个功能。
当然,不能使用 atoi 或者其他类似的库函数。
数据范围 输入字符串长度 [0,20]。
样例 输入:"123"
输出:123 注意:
你的函数应满足下列条件:
忽略所有行首空格,找到第一个非空格字符,可以是 ‘+/−’ 表示是正数或者负数,紧随其后找到最长的一串连续数字,将其解析成一个整数; 整数后可能有任意非数字字符,请将其忽略; 如果整数长度为 0,则返回 0; 如果整数大于 INT_MAX(231−1),请返回 INT_MAX;如果整数小于INT_MIN(−231) ,请返回 INT_MIN;
stringstream字符串输入输出流【string-->int】
class Solution {
public:
int strToInt(string str) {
stringstream s;
int res = 0;
if(str=="") return 0;
s << str;
s >> res;
return res;
}
};
sstream : stringstream
stringstream s; // <sstream>
int res;
s << 114;
s << 514; //没有清空不会覆盖只会拼接 : ss == "114514"
s >> res; //res = 114514
s.clear(); //好习惯:判断下一个数据前, 清空s
特殊判断 + 符号 + ASCLL
class Solution {
public:
int strToInt(string str) {
int k = 0;
while (k < str.size() && str[k] == ' ') k ++ ; //去除前导空字符
long long res = 0;
bool is_minus = false;//标记正负
if (k < str.size())
{
if (str[k] == '-') is_minus = true, k ++ ;
else if (str[k] == '+') k ++ ;
}
while (k < str.size() && str[k] >= '0' && str[k] <= '9')
{
res = res * 10 + str[k] - '0'; //秦九昭
if (res > 1e11) break; //1ell隐式转long long
k ++ ;
}
if(is_minus) res = -res; //确定符号
if (res > INT_MAX) res = INT_MAX;
if (res < INT_MIN) res = INT_MIN;
return res;
}
};
y总代码
class Solution {
public:
int strToInt(string str) {
int k = 0;
while (k < str.size() && str[k] == ' ') k ++ ;
long long res = 0;
int minus = 1;//标记正负
if (k < str.size())
{
if (str[k] == '-') minus = -1, k ++ ;
else if (str[k] == '+') k ++ ;
}
while (k < str.size() && str[k] >= '0' && str[k] <= '9')
{
res = res * 10 + str[k] - '0'; //str[k] - '0' ,【 进位 + ASCLL转换】
if (res > 1e11) break;
k ++ ;
}
res *= minus; //确定符号
if (res > INT_MAX) res = INT_MAX;
if (res < INT_MIN) res = INT_MIN;
return res;
}
};
84. 求1+2+…+n
不用乘除法
a分配n*(n+1)个
char型(1字节)
, sizeof(a) = n * (n + 1) 字节,
a二进制数右移1位 >> 1 等效 十进制数a / 2
class Solution {
public:
int getSum(int n) {
char a[n][n+1];
return sizeof(a) >> 1;
}
};
高斯NB
注意
:多个乘除中:先除可以防溢出,但是可能计算错误【比如此题】 int型可能,但浮点数如double型可以先除再乘不会错
class Solution {
public:
int getSum(int n) {
return (long)(n + 1) * n / 2 ; //先除2可以防溢出,但是(int)1/2 == 0 ; 达到2.5e11 开long
}
};
递归
class Solution {
public:
int getSum(int n) {
int res = n;
(n > 0) && (res += getSum(n - 1));//利用逻辑运算符短路性质终止递归!![语法:其实这样写难为自己, 但NB]
return res;
}
};
28. 在O(1)时间删除链表结点
给定单向链表的一个节点指针,定义一个函数在O(1)时间删除该结点。
假设链表一定存在,并且该节点一定不是尾节点。
数据范围 链表长度 [1,500]。
样例 输入:链表 1->4->6->8 删掉节点:第2个节点即6(头节点为第0个节点)
输出:新链表 1->4->8
将下一个节点的值复制到当前节点,然后将下一个节点删除即可。 时间复杂度 只有常数次操作,所以时间复杂度是 O(1)。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
void deleteNode(ListNode* node) {
auto p = node->next;
//node->val = p->val;
//node->next = p->next;
// 这两步的作用就是将 *(node->next) 赋值给 *node,所以可以合并成一条语句:
*node = *(node->next);
delete p; //好习惯,释放野指针
}
};
36. 合并两个排序的链表
输入两个递增排序的链表,合并这两个链表并使新链表中的结点仍然是按照递增排序的。
数据范围 链表长度 [0,500]。
样例 输入:1->3->5 , 2->4->5
输出:1->2->3->4->5->5
每次加入未选择的最小值-头插法[二路归并]
class Solution {
public:
ListNode* merge(ListNode* l1, ListNode* l2) { //前提:两个有序链表
auto dummy = new ListNode(-1), tail = dummy;
while (l1 && l2 ) { //其中一个遍历完,停止
if (l1 -> val < l2 -> val) { //放入值较小的, tail更新指向其位置
tail = tail -> next = l1;
l1 = l1 -> next;
}
else {
tail = tail -> next = l2; //等效tail->next = l2, tail = tail->next;
l2 = l2 -> next;
}
}
if(l1) tail->next = l1; //剩下的
if(l2) tail->next = l2;
return dummy->next;
}
};
二路归并 O(n)
新建头部的保护结点dummy(虚拟),设置cur指针指向dummy。 若当前l1指针指向的结点的值val比l2指针指向的结点的值val小,则令cur的next指针指向l1,且l1后移;否则指向l2,且l2后移。 然后cur指针按照上一部设置好的位置后移。 循环以上步骤直到l1或l2为空。 将剩余的l1或l2接到cur指针后边。 时间复杂度 两个链表各遍历一次,所以时间复杂度为O(n)
// 与cur与tail相同-换一种写法风格
class Solution {
public:
ListNode* merge(ListNode* l1, ListNode* l2) {
ListNode *dummy = new ListNode(0);
ListNode *cur = dummy;
while (l1 != NULL && l2 != NULL) {
if (l1 -> val < l2 -> val) {
cur -> next = l1;
l1 = l1 -> next;
}
else {
cur -> next = l2;
l2 = l2 -> next;
}
cur = cur -> next;
}
cur -> next = (l1 != NULL ? l1 : l2);
return dummy -> next;
}
};
35. 反转链表
定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头结点。
思考题:
请同时实现迭代版本和递归版本。 数据范围 链表长度 [0,30]。
样例 输入:1->2->3->4->5->NULL
输出:5->4->3->2->1->NULL
mycode2022
class Solution {
public:
ListNode* reverseList(ListNode* head) {
auto tail = head;
ListNode* prev = nullptr; //不能auto 【左边是nullptr, 不被auto识别为ListNode*】
while(tail)
{
auto next = tail->next;
tail->next = prev;
prev = tail, tail = next; //tail = next等效tail = tail->next;
}
return prev;
}
};
Y总链接 链表操作-迭代 O(n)
翻转即将所有节点的next指针指向前驱节点。 由于是单链表,我们在迭代时不能直接找到前驱节点,所以我们需要一个额外的指针保存前驱节点。 同时在改变当前节点的next指针前,不要忘记保存它的后继节点。
空间复杂度分析:遍历时只有3个额外变量,所以额外的空间复杂度是 O(1)。 时间复杂度分析:只遍历一次链表,时间复杂度是 O(n)。
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode *prev = nullptr; //
ListNode *cur = head;
while (cur)
{
ListNode *next = cur->next; //next存cur->next
cur->next = prev;//head->next逆转后指向空,之后 1 <-- 2, 2 <-- 3 ......
prev = cur, cur = next;
}
return prev;
}
};
链表操作-递归 O(n)
首先我们先考虑 reverseList 函数能做什么,它可以翻转一个链表,并返回新链表的头节点,也就是原链表的尾节点。 所以我们可以先递归处理 reverseList(head->next),这样我们可以将以head->next为头节点的链表翻转, 并得到原链表的尾节点tail,此时head->next是新链表的尾节点, 我们令它的next指针指向head,并将head->next指向空即可将整个链表翻转,且新链表的头节点是tail。
空间复杂度分析:总共递归 n 层,系统栈的空间复杂度是 O(n),所以总共需要额外 O(n)的空间。 时间复杂度分析:链表中每个节点只被遍历一次,所以时间复杂度是 O(n)。 [过程简单:两个相邻点的next逆转方向, 边界修改需理解]
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (!head || !head->next) return head;//为空停止
ListNode *tail = reverseList(head->next);
head->next->next = head;//新链表尾结点head->next的next指向head
head->next = nullptr;//指向空
return tail;
}
};
bolg补充 C和C++中的NULL不等价: NULL表示指针不指向任何对象,但是问题在于,NULL不是关键字,而只是一个宏定义(macro)。 在C++中,NULL却被明确定义为整常数0:
66. 两个链表的第一个公共结点
输入两个链表,找出它们的第一个公共结点。
当不存在公共节点时,返回空节点。
数据范围
链表长度 [1,2000]。
样例
给出两个链表如下所示:
A: a1 → a2
↘
c1 → c2 → c3
↗
B: b1 → b2 → b3
输出第一个公共节点c1
简写
【注释为数据结构定义,考研要写】
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *findFirstCommonNode(ListNode *headA, ListNode *headB) {
auto p = headA, q = headB;
while (p != q) { //找到p == q停止
p = p ? p->next : headB; //p非空往下走,否则(走到末尾)再往B走
q = q ? q->next : headA;
}
return p;
}
};
方法1:不同部分为a和b,公共部分为c :a + c + b = b + c + a; 让两个一起走, a走到头就转向b, b走到头转向a,则走a+b+c步后必在公共部分相遇。
class Solution {
public:
ListNode *findFirstCommonNode(ListNode *headA, ListNode *headB) {
auto p = headA, q = headB;
while(p != q) {
if(p) p = p->next;
else p = headB;
if (q) q = q->next;
else q = headA;
}
return p;
}
};
方法2:先计算出两个链表的长度,可以让比较长的先走两个链表长度之差的步数,两个再一起走就同时到公共节点。
class Solution {
public:
ListNode *findFirstCommonNode(ListNode *headA, ListNode *headB) {
auto p = headA, q = headB;
int la = 0, lb = 0;
for (auto t = headA; t; t = t->next) la ++;
for (auto t = headB; t; t = t->next) lb ++;
int k = la - lb;
if (la < lb) {
p = headB, q = headA;
k = lb - la;
}
while(k --) {
p = p->next;
}
while(p) {
if (p == q) return p;
p = p->next;
q = q->next;
}
return nullptr;
}
};
29. 删除链表中重复的节点
在一个排序的链表中,存在重复的节点,请删除该链表中重复的节点,重复的节点不保留。
数据范围 链表中节点 val 值取值范围 [0,100]。 链表长度 [0,100]。
样例1 输入:1->2->3->3->4->4->5
输出:1->2->5 样例2 输入:1->1->1->2->3
输出:2->3
线性扫描 O(n)
指针修改my理解:指针会直接改变指向地址单元的next指针域存放的指向地址
思路:[一个指针q向前探索到非重复段的第一个元素位置,再让另一个指针p指向它]
为了方便处理边界情况,我们定义一个虚拟元素 dummy 指向链表头节点。 然后从前往后扫描整个链表,每次扫描元素相同的一段,如果这段中的元素个数多于1个,则将整段元素直接删除。
整个链表只扫描一遍, 所以时间复杂度是 O(n)。
class Solution {
public:
ListNode* deleteDuplication(ListNode* head) {
auto dummy = new ListNode(-1); //虚拟头结点dummy, 可以保证头结点不被删去
dummy->next = head;
auto p = dummy;
while (p->next) { //从第一个元素[首结点]开始判断
auto q = p->next; //q一直走判断与当前位p->next->val的关系
while (q && p->next->val == q->val) q = q->next; //若有重复元素, p->next->val != q->val的位置(跳过重复段)
if (p->next->next == q) p = p->next; // p与q之间只隔一个点保留 p指向此点
else p->next = q; //删除重复元素段:直接p->next指向q代表的不重复位置
}
return dummy->next; //返回头结点地址,即整段链表
}
};