文章目录
刷题记录
27.移除元素
方法一:
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
for(int i = 0; i < nums.size(); i++)
{
if(nums[i] == val)
{
for(int j = i; j < nums.size()-1; j++)
{
nums[j] = nums[j+1];
}
nums.resize(nums.size()-1);
i--;
}
}
return nums.size();
}
};
这是自己写出来的方法,意思是先枚举数组每一个元素,当这个元素==val时,就将后面的元素全部往前挪一个位置,这样的时间复杂度是On方的
方法二:(快慢指针)
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int i = 0, j = 0;
while(j < nums.size())
{
if(nums[j] != val) nums[i++] = nums[j++];
else j++;
}
return i;
}
};
i是慢指针,j是快指针,当nums[j]不是val时就将nums[j]覆盖nums[i],这样就只使用了On的时间复杂度完成了这一题
977.有序数组的平方
方法一:
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
vector<int> res;
for(int i = 0; i < nums.size(); i++)
{
res.push_back(nums[i]*nums[i]);
}
sort(res.begin(), res.end());
return res;
}
};
这个方法就是取巧使用了sort函数,先将所有元素的平方值存入数组中,最后使用sort函数将其升序排序。
方法二:(双指针)
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
vector<int> res(nums.size());
int i = 0, j = nums.size()-1, n = nums.size();
while(i <= j)
{
if(abs(nums[i]) < abs(nums[j]))
{
res[--n] = nums[j]*nums[j];
j--;
}
else
{
res[--n] = nums[i]*nums[i];
i++;
}
}
return res;
}
};
考虑到给出的数组是有序的,那么左右两边的值取平方一定是放在新数组的末尾,所以这里我们就定义两个指针,一个在头,一个在尾,相向遍历数组,直到i下标大于j下标就退出循环。当然这里需要将新数组从后往前录入数据,所以需要事先开辟好res数组的空间。
209.长度最小的子数组
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int left = 0, sum = 0, ans = nums.size()+1;
for(int right = 0; right < nums.size(); right++)
{
sum += nums[right];
while(sum >= target)
{
ans = min(ans, right-left+1);
sum -= nums[left++];
}
}
return ans == nums.size()+1 ? 0 : ans;
}
};
这道题运用的方法也是双指针滑动窗口,外层循环枚举数组每个元素,内层循环用来使符合的子数组长度最小,只要这段子数组的和一直>=target,左指针就不断的右移并更新ans长度和减去左边界的值。
707.设计链表
class MyLinkedList {
public:
struct ListNode
{
int val;
ListNode* next;
ListNode(int num) : val(num), next(nullptr){}
};
MyLinkedList() {
dummyhead = new ListNode(0);
Size = 0;
}
int get(int index) {
if(index+1 > Size || index < 0) return -1;
ListNode* pmove = dummyhead;
while(index--) pmove = pmove->next;
return pmove->next->val;
}
void addAtHead(int val) {
ListNode* s = new ListNode(val);
s->next = dummyhead->next;
dummyhead->next = s;
Size++;
}
void addAtTail(int val) {
ListNode* pmove = dummyhead;
while(pmove->next) pmove = pmove->next;
ListNode* s = new ListNode(val);
pmove->next = s;
Size++;
}
void addAtIndex(int index, int val) {
if(index > Size) return;
ListNode* pmove = dummyhead;
while(index--) pmove = pmove->next;
ListNode* s = new ListNode(val);
s->next = pmove->next;
pmove->next = s;
Size++;
}
void deleteAtIndex(int index) {
if(index >= Size || index < 0) return;
ListNode* pmove = dummyhead;
while(index--) pmove = pmove->next;
pmove->next = pmove->next->next;
Size--;
}
private:
int Size;
ListNode* dummyhead;
};
/**
* Your MyLinkedList object will be instantiated and called as such:
* MyLinkedList* obj = new MyLinkedList();
* int param_1 = obj->get(index);
* obj->addAtHead(val);
* obj->addAtTail(val);
* obj->addAtIndex(index,val);
* obj->deleteAtIndex(index);
*/
这个题让设计一个链表,包括链表的基本操纵,基本的插入删除操作还是没有忘,只不过在一些细节上不是很完美,首先定义一个结构体,因为是链表加上题目给的数据是int类型的,所以val是数据域,next是指针域。定义两个私有成员变量,大小size和虚拟头节点dummyhead。我感觉这个题ac不了的问题在于不能明确结构体结点和类之间的差别。
206.反转链表
双指针迭代法:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* cur = head, *pre = nullptr, *temp;
while(cur != nullptr)
{
temp = cur->next;
cur->next = pre;
pre = cur;
cur = temp;
}
return pre;
}
};
这里最好是将cur = head, pre = nullptr,因为第一个结点在反转完链表后要置为空的,如果是pre = head, cur = head->next的话,会导致代码变得复杂,需要单独将第一次操作提出来,还需要判断head是否为空。
递归法:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* reverse(ListNode* cur, ListNode* pre)
{
if(cur == nullptr) return pre;
ListNode* temp = cur->next;
cur->next = pre;
return reverse(temp, cur);
}
ListNode* reverseList(ListNode* head) {
return reverse(head, nullptr);
}
};
递归理解起来可能会比较晦涩,其实本质上也是双指针迭代,两个参数初始化cur=head, pre = nullptr,递归终点也是在cur==nullptr的时候退出,这里在写的时候都需要注意的是:需要借助一个临时变脸来记录cur->next,否则会导致死循环或者递归结果不正确,那是因为在第二次循环或递归之前会有cur->next=pre的操作。
24.两两交换链表中的节点
解法一:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
void exchange(ListNode* node)
{
if(node == nullptr || node->next == nullptr) return;
int temp = node->val;
node->val = node->next->val;
node->next->val = temp;
exchange(node->next->next);
}
ListNode* swapPairs(ListNode* head) {
//if(head == nullptr) return nullptr;
exchange(head);
return head;
}
};
这也是我自己的解法,当然是在捡漏,因为每个节点的val值一定是相同的,所以我就只改动了数据域而非指针域,但是当数据域多的话用这个方法就显得十分笨拙。
解法二:
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* dummyhead = new ListNode(0, head);
ListNode* cur = dummyhead;
while(cur->next != nullptr && cur->next->next != nullptr)
{
ListNode* temp1 = cur->next;
ListNode* temp2 = cur->next->next->next;
cur->next = cur->next->next;
cur->next->next = temp1;
temp1->next = temp2;
cur = cur->next->next;
}
return dummyhead->next;
}
};
这个解法就是修改指针域,首先定义一个虚拟头节点dummyhead,假设节点a->b->c当cur指向a的时候才能将b和c的指针域进行交换,以此类推,如何判断循环退出的条件?当链表为奇数个数时:cur->next->next == nullptr是循环退出的标志,当链表个数为偶数个时:cur->next == nullptr为退出标志。注意cur->next一定要在cur->next->next的前面,否则会给出空指针异常。再就是更改指针的逻辑,一定要确定好更改的顺序,时常要用到临时的指针变量进行保存位置,否则连接就会混乱。工作指针移动的条件是cur = cur->next->next,忘写会时间超限,并且一定不能出错。最后返回虚拟头节点的next即为最后的答案。
19.删除链表的倒数第N个节点
解法一:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyhead = new ListNode(0, head);
int num = 0;
ListNode* pmove = head;
while(pmove)
{
num++;
pmove = pmove->next;
}
int pos = num-n;
cout << pos;
ListNode* p = dummyhead;
int count = 0;
while(p)
{
if(count == pos)
{
p->next = p->next->next;
break;
}
count++;
p = p->next;
}
return dummyhead->next;
}
};
这当然也是最笨拙的方法,遍历两边链表。
解法二:
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyhead = new ListNode(0, head);
ListNode* fast = dummyhead, *low = dummyhead;
int count = n+1;
while(count-- && fast) fast = fast->next;
while(fast)
{
fast = fast->next;
low = low->next;
}
low->next = low->next->next;
return dummyhead->next;
}
};
这里只遍历了一遍链表,果然在遇到链表题的时候双指针有时候真的有奇效啊。这里先用fast指针遍历前n+1个元素,然后再让fast指针和low指针一起移动,直到fast指针移到末尾,这时low指针就指向了要删除元素的前一个位置,最后low->next = low->next->next达到删除链表倒数第N个节点的效果。
142.环形链表II
解法一:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
unordered_set<ListNode*> visited;
ListNode* pmove = head;
while(pmove)
{
if(find(visited.begin(), visited.end(), pmove) != visited.end()) return pmove;
visited.insert(pmove);
pmove = pmove->next;
}
return nullptr;
}
};
这道题用的是哈希表,唉我看到题的第一映像也是用哈希表,可是我就在想,如果遇到了重复的元素值那就哈希表就不能达到效果,那么为什么这个哈希表却能用呢?原来这个是将哈希表的类型定义成节点类型的指针,这就说得通了,虽然节点的val值可能会相同,但是每个节点所存储的地址一定是不同的,当遍历到哈希表有存储过的节点时,这个节点就是我们要找的环形链表的起始位置。
这个题告诉了我们什么?其实你的方法可能是正确的,你其实离真相很近,但是你却中途选择了放弃,当然这样你也与成功失之交臂了。
解法二:
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* fast = head, *low = head;
while(true)
{
if(fast == nullptr || fast->next == nullptr) return nullptr;
fast = fast->next->next;
low = low->next;
if(fast == low) break;
}
fast = head;
while(fast != low)
{
fast = fast->next;
low = low->next;
}
return fast;
}
};
这个呢主要是用到了数学的关系式,这对于我这个数学菜鸡来说简直就是天方夜谭,主要步骤就是让fast指针每次走两个节点,low指针每次走一个节点,这样如果有环的话fast指针一定会追上low指针,然后再将fast指针置为head再次移动fast和low,此时两个指针都是一个节点一个节点地移动,直到fast和low相等的时候就是我们要找到的目标位置。
242.有效的字母异位词
class Solution {
public:
//a = 97
bool isAnagram(string s, string t) {
vector<int> hash(26, 0);
for(char w : s) hash[w-'a']++;
for(char w : t) hash[w-'a']--;
for(int i = 0; i < 26; i++)
{
if(hash[i] != 0) return false;
}
return true;
}
};
这里使用的是哈希表来解决问题,哈希表分为三种:数组、set和map,数组的应用场景通常在能够确定数组大小等简单情景下使用,这里只会出现
349.两个数组的交集
unordered_set写法:
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> hash;
for(int n : nums1) hash.insert(n);
set<int> s;
sort(nums2.begin(), nums2.end());
for(int i = 0; i < nums2.size(); i++)
{
if(hash.find(nums2[i]) != hash.end()) s.insert(nums2[i]);
}
return vector<int>(s.begin(), s.end());
}
};
数组写法:
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
vector<int> hash(1001, 0);
for(int a : nums1) hash[a]++;
set<int> s;
for(int i = 0; i < nums2.size(); i++)
{
if(hash[nums2[i]]) s.insert(nums2[i]);
}
return vector<int>(s.begin(), s.end());
}
};
两种写法都大差不差,都是先遍历nums1数组用哈希表来存储,第二次遍历nums2数组来记录两个数组的交集元素,存储在set中,由于最后返回的是数组,我们用return vector< int >(s.begin(), s.end());来转换。
454.四数相加II
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
unordered_map<int, int> m1, m2;
for(int a : nums1)
{
for(int b : nums2) m1[a+b]++;
}
int count = 0;
for(int a : nums3)
{
for(int b : nums4)
{
int temp = 0-(a+b);
if(m1[temp]) count += m1[temp];
}
}
return count;
}
};
解决过程:将四个数组分成两个对数组,先将前一对数组进行各元素相加,将取得的值存入m1哈希表中,这里采用的哈希表需要记录求和的个数,则需要数值到个数之间的映射,所以这里用到的哈希表为unordered_map,遍历完第一对数组之后,就类似于1.两数相加的题型了,如果在差值在m1中出现过,那么我们就将count累加,注意count = m1[0-(a+b)];而不是count++;
18.四数之和
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> res;
sort(nums.begin(), nums.end());
int n = nums.size();
for(int i = 0; i < n-3; i++)
{
if(i && nums[i] == nums[i-1]) continue;
for(int j = i+1; j < n-2; j++)
{
int left = j+1, right = n-1;
if(i+1 < j && nums[j] == nums[j-1]) continue;
if((long long)nums[i]+nums[j]+nums[j+1]+nums[j+2] > target) break;
if((long long)nums[i]+nums[j]+nums[n-1]+nums[n-2] < target) continue;
while(left < right)
{
long long temp = (long long)nums[i]+nums[j]+nums[left]+nums[right];
if(temp < target) left++;
else if(temp > target) right--;
else
{
res.push_back({nums[i], nums[j], nums[left], nums[right]});
//cout << i << j << left << right;
for(++left; left < right && nums[left] == nums[left-1]; left++);
for(--right; left < right && nums[right] == nums[right+1]; right--);
}
}
}
}
return res;
}
};
这一题的解法与三数之和是同样的,都是用到了双指针的方法,然后外面套两层循环来枚举数组,这里需要注意的点是:在我提交后发现有int类型的溢出错误,我就尝试先将temp的类型改成long long,结果还是报错,查了之后才发现需要在nums数组前面也要强制转化成long long型,这样得出来的数组结果也会向上转型成long long类型。否则在数组累加的过程中会导致先int型溢出了然后无法赋值给temp的情况。
151.反转字符串中的单词
class Solution {
public:
void func(string& s, int left, int right)
{
while(left < right) swap(s[left++], s[right--]);
}
void deletespace(string& s)
{
//i快指针 j慢指针
int i, j = 0;
for(i = 0; i < s.size(); i++)
{
//快指针非空 进行替换操作
if(s[i] != ' ')
{
//在每个单词之前手动添加空格
if(j != 0) s[j++] = ' ';
//进行替换
while(i < s.size() && s[i] != ' ') s[j++] = s[i++];
}
}
s.resize(j);
}
string reverseWords(string s) {
//先除去非法空格
deletespace(s);
//反转所有字符串
func(s, 0, s.size()-1);
//反转每个单词之间的字符
int start = 0;
for(int i = 0; i <= s.size(); i++)
{
if(i == s.size() || s[i] == ' ')
{
func(s, start, i-1);
start = i+1;
}
}
return s;
}
};
首先反转单词和反转字符十分像,但是需要发现怎么将反转字符的方法用到反转单词上,这里给出的方法是先将字符串s整体反转,然后再将单词之间内部再次反转,,这样就达到了将字符串中的单词反转的效果了。这里还有一个算法,就是去除不合法的空格:用到的是快慢双指针,与前面的27.移除元素是一个道理,移除元素是将元素的某个val值删除,这里删除空格也就是可以看成将一个数组全部为char型,然后删除字符’ '即可。只不过英语语句有这样一个特点:句子没有前缀空格,单词与单词之间有一个空格,所以如果慢指针j != 0需要在替换之前先添加空格。
459.重复的子字符串
class Solution {
public:
void getNext(int next[], string s)
{
next[0] = 0;
int j = 0;
for(int i = 1; i < s.size(); i++)
{
while(j > 0 && s[j] != s[i]) j = next[j-1];
if(s[i] == s[j]) j++;
next[i] = j;
}
}
bool repeatedSubstringPattern(string s) {
if(s.empty()) return false;
int n = s.size();
int next[n];
getNext(next, s);
if(next[n-1] != 0 && n%(n-next[n-1]) == 0) return true;
return false;
}
};
这里用到的是kmp算法,getNext函数求相等前后缀表,这个理解起来是在困难,还是先先记下来为妙吧。。。while那行一定不能写成if,也不能少了j>0的条件,这一句是在不相等的情况下。if语句是在两个字符相等的情况下,循环最后将当前next[i]值赋值成j,即当前下标的前后缀相等数。然后就是主函数中判断题目中给出的意思,n-next[n-1]是用来找出最短的符合条件的子串,然后如果能被总长度整除,那么这个字符串可以由某个子串重复构成。这怎么会是简单题???可能简单在于比较好暴力吧,感觉这个kmp还是挺难理解的,加上最后需要取余来判断是否满足条件,这也是很难想到的。