这一章类似于复习。
一天1-2题
要求:
1.使用c++中的debug语句判断。
2.调用库函数时尽量知道其原理。
1.移除元素
这个算是复习,但是我的第一反应也想不起来。
1.我首先想到的是:把要删除的元素移到尾部。
2.后来复习代码随想录的讲解之后,使用代码随想录的想法,使用的是while循环。
3.代码随想录中,for循环写的效果非常好,非常精简。
我的想法:
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int len = nums.size();
int slowIndex = 0;
int fastIndex = len - 1;
while (slowIndex<=fastIndex) {
if (nums[slowIndex] == val)
{
while(nums[slowIndex] == nums[fastIndex])
{
if (fastIndex ==slowIndex) {
return slowIndex;
}
fastIndex--;
}
nums[slowIndex++] = nums[fastIndex--];
}
else {
slowIndex++;
}
}
return slowIndex;
}
};
我回顾代码随想录的想法后:
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int len = nums.size();
int slowIndex = 0;
int fastIndex = 0;
while(fastIndex<len) {
if(nums[fastIndex] == val) {
nums[slowIndex] = nums[fastIndex++];
}
else {
nums[slowIndex++] = nums[fastIndex++];
}
}
return slowIndex;
}
};
代码随想录的写法:
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int len = nums.size();
int slowIndex = 0;
for(int fastIndex = 0; fastIndex < len; fastIndex++) {
if (nums[fastIndex] != val) {
nums[slowIndex++] = nums[fastIndex];
}
}
return slowIndex;
}
};
2.反转字符串
这个就比较简单了。
class Solution {
public:
void reverseString(vector<char>& s) {
for (int left = 0, right = s.size() - 1; left < s.size() / 2; left++, right--) {
swap(s[left],s[right]);
}
}
};
3.替换数字
很简单:不过单个字符应该用单引号括起来,不能用双引号。
#include <iostream>
using namespace std;
int main() {
string inputs;
cin >> inputs;//遇到空格会自动结束
int len = inputs.size();
int nums{ 0 };
for (int i = 0; i < len; i++) {
if (inputs[i] >= '0' && inputs[i] <= '9') {
nums++;
}
}
//修改总字符串长度,C++的string有这个功能
inputs.resize(nums * 5 + len);
int tailIndex = inputs.size() - 1;
//字符串长度修改成功
for (int i = len-1; i >= 0; i--) {
if (inputs[i] >= '0' && inputs[i] <= '9') {
inputs[tailIndex--] = 'r';
inputs[tailIndex--] = 'e';
inputs[tailIndex--] = 'b';
inputs[tailIndex--] = 'm';
inputs[tailIndex--] = 'u';
inputs[tailIndex--] = 'n';
}
else {
inputs[tailIndex--] = inputs[i];
}
}
cout << inputs;
return 0;
}
4.反转字符串中的单词
难点还是删除多余的空格这部分比较麻烦,不好理解。
两个思路:
1.是将连续空格放在while中处理
2.将连续字符串放在while中处理(这种简洁很多)
class Solution {
public:
string reverseWords(string s) {
//两个思路,先清除所有空格,再缩减尺寸,再整体反转,再遇到空格反转
//第一步:清除空格
int len = s.size();
int updateIndex{ 0 };
//用双指针法
for (int i = 0; i < len; i++) {
if (s[i] != ' ') {
if(updateIndex > 0) s[updateIndex++] = ' ';
while (s[i] != ' '&& i < len) {
s[updateIndex++] = s[i++];
}
}
}
//缩减长度
s.resize(updateIndex);
//反转
reverse(s.begin(),s.end());
//发现空格即反转
int start{0};
for(int i = 0; i < s.size(); i++) {
if (s[i] == ' ') {
reverse(s.begin() + start,s.begin() + i);
start = i + 1;
}
if (i == s.size() - 1) {
reverse(s.begin() + start,s.end());
}
}
return s;
}
};
5.翻转链表
这次是第二次做,顺序循环可以做到,但是两个递归方法确实不好理解,以后还得加深这道题的印象,值得反复。尤其是两个递归方法。
警醒:以后遇到可顺序循环的地方努力尝试去用递归算法。思路比较特殊,值得掌握学习。
顺序循环版本:
class Solution {
public:
ListNode* reverseList(ListNode* head) {
//1.正常顺序 2.递归 3.递归
ListNode* pre = nullptr;
ListNode* temp;
while (head!=nullptr) {
temp = head;
head = head->next;
temp->next = pre;
pre = temp;
}
return pre;
}
};
递归版本1:
递归书写先考虑终止条件再按顺序编写
class Solution {
public:
ListNode* reverse(ListNode* prev, ListNode* head) {
//1.正常顺序 2.递归 3.递归
//终止条件
if(head == nullptr){
return prev;
}
ListNode* temp;
temp = head;
head = head->next;
temp->next = prev;
prev= temp;
return reverse(prev, head);
}
ListNode* reverseList(ListNode* head) {
//1.正常顺序 2.递归 3.递归
return reverse(nullptr,head);
}
};
递归版本3:直接从末尾向前反转,不好理解,但是思路非常简洁。
class Solution {
public:
ListNode* reverseList(ListNode* head) {
//1.正常顺序 2.递归 3.递归
//终止条件:
if(head == nullptr && head -> next = nullptr) {
return head;
}
ListNode* newhead = reverseList(head->next);
head->next->next = head;
head->next = nullptr;
return newhead;
}
};
6.删除链表倒数第n个节点
特别要考虑的点是:while (fastNode -> next != nullptr) 以及while (fastNode != nullptr)这两个边缘条件要考虑清楚。
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyNode = new ListNode();
dummyNode->next = head;
ListNode* slowNode = dummyNode;
ListNode* fastNode = dummyNode;
ListNode* deleteNode = nullptr;
while (n--) {
fastNode = fastNode -> next;
}
while (fastNode -> next != nullptr) {
fastNode = fastNode->next;
slowNode = slowNode->next;
}
deleteNode = slowNode -> next;
slowNode->next = slowNode->next->next;
delete deleteNode;
head = dummyNode->next;
delete dummyNode;
return head;
}
};
7.链表相交
思路很妙。
class Solution {
public:
ListNode* getIntersectionNode(ListNode* headA, ListNode* headB) {
//代码随想录中的思路太妙了
int headALen{ 0 }, headBLen{ 0 };
ListNode* aList = headA;
ListNode* bList = headB;
while (aList != nullptr) {
headALen++;
aList= aList -> next;
}
while (bList != nullptr) {
headBLen++;
bList = bList -> next;
}
int cha = abs(headALen - headBLen);
ListNode* searchNodeLong = headALen >= headBLen ? headA : headB;
ListNode* searchNodeShort = headALen >= headBLen ? headB : headA;
while (cha--) {
searchNodeLong = searchNodeLong -> next;
}
while (searchNodeLong != nullptr) {
if (searchNodeLong == searchNodeShort) {
return searchNodeLong;
}
searchNodeLong=searchNodeLong -> next;
searchNodeShort = searchNodeShort -> next;
}
return NULL;
}
};
8.环形链表II
重点关注一下这道题,比较复杂。
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
//用快慢指针的跟踪方法
ListNode* fast = head;
ListNode* slow = head;
while(fast != nullptr && fast->next != nullptr) {
fast = fast->next->next;
slow = slow->next;
if(fast == slow) {
ListNode* index1 = fast;
ListNode* index2 = head;
while (index1!=index2) {
index1 = index1->next;
index2 = index2->next;
}
return index1;
}
}
return nullptr;
}
};
9.三数之和
二刷这道题的时候,还是没能独立解决掉,依然用到的是代码随想录的想法:
重难点:
1.去重(第一个数字,第二个数字,第三个数字)
2.&&和||的写法:当&&的前半部分为0,则直接为0;当||前半部分为1,则直接为1.
举例:
第一种:while (left < right && nums[left] == nums[left + 1] )
第二种:while (nums[left] == nums[left + 1]&&left < right )//堆栈溢出风险
第二种写法是有堆栈溢出的风险的。
很重要的一道题,非常值得多,反复思考,编写。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) { //三数之和,输入是一个数组,输出是一组三元组
vector<vector<int>> result;
sort(nums.begin(),nums.end());//升序排序
for (int i = 0; i < nums.size()-2; i++) {
if (nums[i] > 0) {
//不用再继续找下去了
return result;//还得修改
}
if (i > 0 && nums[i] == nums[i-1]) {
continue;
}
int left = i + 1;
int right = nums.size() - 1;
while (left < right) {
if (nums[i] + nums[left] + nums[right] > 0) {
right--;
}
else if (nums[i] + nums[left] + nums[right] < 0) {
left++;
}
else {
result.push_back({nums[i], nums[left], nums[right]});
while (left < right && nums[left] == nums[left + 1] ) {
left++;
}
while (left < right && nums[right] == nums[right - 1]) {
right--;
}
left++;
right--;
}
}
}
return result;
}
};
10.四数之和
跟三数之和是一个思路,唯一 要多考虑的是target可以为负数。
重要的思路:1.left和right的设定,两端往中间夹得方式效率是最高的。2.去重的思路很重要
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
//跟三数之和有异曲同工之妙
vector<vector<int>> result;
//先对整体进行排序
sort(nums.begin(), nums.end());
if (nums.size() < 4) {
return result;
}//这个判断是用来处理短数组的,虽然下面的for循环已经把这种情况包含在内了,这段代码其实可以不加的
for (int i = 0; i < nums.size(); i++) {//注意nums.size()不要再去减去某值了,减去某值必须要考虑到数组长度。
if (target >= 0 && nums[i] > target) {
return result;
}
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
for (int j = i + 1; j < nums.size(); j++) {
if(target - nums[i] >= 0 && nums[j] > target - nums[i]) {
break;
}
if( j > i + 1 && nums[j] == nums[j - 1]) {
continue;
}
int left = j + 1;
int right = nums.size() - 1;
while (left < right) {
if (static_cast<long>(nums[i]) + nums[j] + nums[left] + nums[right] > target) {
right--;
}
else if (static_cast<long>(nums[i]) + nums[j] + nums[left] + nums[right] < target) {
left++;
}
else {
result.push_back({nums[i], nums[j], nums[left], nums[right]});
left++;
right--;
while (left < right && nums[left] == nums[left - 1]) {
left++;
}
while (left < right && nums[right] == nums[right + 1]) {
right--;
}
}
}
}
}
return result;
}
};