1 数组
1.1 【双指针】删除有序数组中的重复项
https://leetcode.cn/problems/remove-duplicates-from-sorted-array/
这道题目用到了双指针,下面结合代码来讲解一下是怎么用
- i:目前所遍历到的数组的位置
- j:目前应该进行替换的位置
刚开始是i和j都指向数组的第二个元素(如果有两个的话,没有直接输出1),从数组的第二个位置开始,如果此时的数字与前面(num)相同,则i继续向后遍历,直到与num不同,此时把i所在位置的数字赋值给j所在位置和num,然后j向后移动一位,重复上述操作,直到遍历到数组的最后一个元素为止。
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int length = nums.size();
int i = 1, j = 1, num = nums[0];
int ans = 1;
for(;i<length;i++)
{
if(nums[i] != num)
{
nums[j] = nums[i];
num = nums[j];
j++;
ans++;
}
else continue;
}
return ans;
}
};
1.2 【双指针】【贪心】买卖股票的最佳时机 II
https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/
1.2.1 双指针
还是继续使用了双指针,low_price指的是目前遍历到的最低价格,初始为第一个元素,high_price指的是目前遍历到的最高价格,当找到最高价格的时候,说明产生了利润,这是计算本次的利润,并修改两个指针的值再次向后遍历,直到遍历完所有情况,不过这里还要考虑一下特殊情况,比如如果股票价格一天比一天低,最后利润应该为0,在这里定义了一个flag用来判断这个特殊情况。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int low_price = prices[0];
int high_price = 0;
int sum = 0;
int length = prices.size();
bool flag = false;
for(int i=1;i<length;i++)
{
if(prices[i] < low_price)
{
low_price = prices[i];
flag = false;
}
else
{
high_price = prices[i];
sum = sum + high_price - low_price;
low_price = prices[i];
flag = true;
}
}
if(sum==0 && !flag) return 0;
else return sum;
}
};
1.2.2 贪心
贪心算法是一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是最好或最优的算法。对于本题而言,我们希望买卖股票可以挣钱,所以可以把这个问题分成若干个子问题,最终问题是如何在整个时间内收益最大,而子问题是把整个时间分为若干份,在每一份时间内保持最大收益,这里我的子问题是每两天计算一次收益,如果收益大于0就计入总收益,因为最后的总收益也是这些子问题内收益大于0的求和。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int length = prices.size();
int sum = 0;
for(int i=1;i<length;i++)
{
int ssum = prices[i] - prices[i-1];
if(ssum > 0) sum += ssum;
else continue;
}
return sum;
}
};
1.3 【暴力】轮转数组
https://leetcode.cn/problems/rotate-array/
暴力解决,遍历元素挨个赋值,如下所示
class Solution {
public:
void rotate(vector<int>& nums, int k) {
int n = nums.size();
vector<int> ans = nums;
for(int i=0;i<n;i++)
{
nums[(i+k)%n] = ans[i];
}
}
};
1.4 【位运算】只出现一次的数字
这个题目我想的有点讨巧了,因为重复的数字只出现两次,基本上一加一减就没了,所以先对数组进行排序,然后遍历时判断与前一个元素是否相同,不同加相同减,最后剩下的那个就是只出现一次的数字(貌似好像只适合这道题)
https://leetcode.cn/problems/single-number/
class Solution {
public:
int singleNumber(vector<int>& nums) {
sort(nums.begin(),nums.end());
int n = nums.size();
int ans = nums[0];
for(int i=1;i<n;i++)
{
if(nums[i] != nums[i-1]) ans += nums[i];
else ans -= nums[i];
}
return ans;
}
};
1.5 【双指针】两个数组的交集 II
https://leetcode.cn/problems/intersection-of-two-arrays-ii/
双指针应该是这道题的常规思路了,先对两个数组进行排序,然后定义两个指针挨个比较大小,把相同元素存到ans中最后输出,不过要考虑好遍历结束的条件,其中一个数组遍历完之后就可以直接return了。
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
vector<int> ans;
sort(nums1.begin(),nums1.end());
sort(nums2.begin(),nums2.end());
int n1 = nums1.size();
int n2 = nums2.size();
for(int i=0,j=0;i<n1,j<n2;)
{
if(i==n1) return ans;
if(j==n2) return ans;
if(nums1[i] > nums2[j]) j++;
else if(nums1[i] < nums2[j]) i++;
else
{
ans.push_back(nums1[i]);
i++;
j++;
}
}
return ans;
}
};
1.6 【暴力】加一
https://leetcode.cn/problems/plus-one/
简单的进位加法题目,从数组的最后一个元素往前遍历即可,满十进一,不过要判断一下数组的第一个元素是否满十进一。
class Solution {
public:
vector<int> plusOne(vector<int>& digits) {
int n = digits.size();
int x = 1;
for(int i=n-1;i>=0;i--)
{
if(digits[i] + x > 9)
{
digits[i] = digits[i] + x - 10;
x = 1;
}
else
{
digits[i] = digits[i] + x;
x = 0;
}
}
if(x==1) digits.insert(digits.begin(),1);
return digits;
}
};
1.7 【双指针】移动零
https://leetcode.cn/problems/move-zeroes/
采用双指针
(1)index为目前指向的元素,从头开始遍历,每次找到不为零的元素之后就赋值给index位置,最后将index之后的所有原始全部赋值为0即可。
(2)定义两个指针left和right,left左边的元素都为非0,left到right之间的元素都为0,刚开始时用right遍历数组,left指向0元素,当right遍历到的元素不为0时,与left交换元素。
//第一种方法
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int n = nums.size();
int index = 0;
for(int i=0;i<n;i++)
{
if(nums[i]!=0)
{
nums[index] = nums[i];
index++;
}
}
for(int j=index;j<n;j++)
{
nums[j] = 0;
}
}
};
//第二种方法
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int n = nums.size();
int left = 0 , right = 0;
while(right < n)
{
if(nums[right])
{
swap(nums[left],nums[right]);
left++;
}
right++;
}
}
};
1.8 【双指针】【哈希表】两数之和
https://leetcode.cn/problems/two-sum/
(1)双指针,首先对数组进行排序,然后使用双指针对数组分别从前后进行遍历,最后把找到的两个数字在原数组中找到对应的下标。
(2)哈希表,遍历数组的时候找一下对应的与target的差值是否在哈希表中,如果在的话就直接返回结果,如果不在的话就把这个元素加入哈希表中继续遍历,这里要注意差值是target-nums[i],要不然搞反了碰见负数就只能找另一个负数了。
//第一种方法---双指针
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
vector<int> nums2 = nums;
sort(nums2.begin(),nums2.end());
int n = nums2.size();
int anum[2];
vector<int> ans;
for(int i=0,j=n-1;;)
{
if(nums2[i] + nums2[j] == target)
{
anum[0] = nums2[i];
anum[1] = nums2[j];
break;
}
else if(nums2[i] + nums2[j] > target) j--;
else i++;
}
for(int i=0;i<n;i++)
{
if(nums[i]==anum[0])
{
ans.push_back(i);
break;
}
}
for(int j=n-1;j>=0;j--)
{
if(nums[j]==anum[1])
{
ans.push_back(j);
break;
}
}
sort(ans.begin(),ans.end());
return ans;
}
};
//第二种方法---哈希表
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int,int> hash_table;
vector<int> ans;
for(int i=0;i<nums.size();i++)
{
int x = target - nums[i];
if(hash_table.count(x))
{
ans.push_back(i);
ans.push_back(hash_table[x]);
return ans;
}
else hash_table[nums[i]] = i;
}
return ans;
}
};
1.9 【双指针】有效的数独
https://leetcode.cn/problems/valid-sudoku/
分别针对每一行、每一列和每个小九宫格用哈希表来判断是否有重复元素,注意小九宫格的九个元素的下标关系。
class Solution {
public:
bool isValidSudoku(vector<vector<char>>& board) {
bool flag = true;
unordered_map<int,int> hash_table;
//判断每行是否true
for(int i=0;i<9;i++)
{
for(int j=0;j<9;j++)
{
if(board[i][j] != '.')
{
int x = board[i][j] - '0';
if(hash_table.count(x)) return !flag;
else hash_table[x] = j;
}
}
hash_table.clear();
}
//判断每列是否true
for(int i=0;i<9;i++)
{
for(int j=0;j<9;j++)
{
if(board[j][i] != '.')
{
int x = board[j][i] - '0';
if(hash_table.count(x)) return !flag;
else hash_table[x] = j;
}
}
hash_table.clear();
}
//判断每个3*3是否true
for(int k=0;k<9;k++)
{
int i_size = (k/3)*3;
int j_size = (k%3)*3;
for(int i=0;i<3;i++)
{
for(int j=0;j<3;j++)
{
if(board[i+i_size][j+j_size] != '.')
{
int x = board[i+i_size][j+j_size] - '0';
if(hash_table.count(x)) return !flag;
else hash_table[x] = (i+1)*(j+1);
}
}
}
hash_table.clear();
}
return flag;
}
};
PS:考虑一下如何只遍历一次即可解决问题。
1.10 【数学】旋转图像
https://leetcode.cn/problems/rotate-image/
先对矩阵进行上下翻转,再对角线进行翻转,主要是理解90度这个翻转规则
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
int n = matrix.size();
//先上下翻转
for(int i=0;i<n/2;i++)
{
vector<int> temp = matrix[i];
matrix[i] = matrix[n-1-i];
matrix[n-1-i] = temp;
}
//对角线翻转
for(int i=0;i<n-1;i++)
{
for(int j=i+1;j<n;j++)
{
int temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
}
}
};
2 字符串
2.1 【双指针】反转字符串
https://leetcode.cn/problems/reverse-string/
利用双指针,一前一后遍历字符串数组,然后交换。
class Solution {
public:
void reverseString(vector<char>& s) {
int n = s.size();
char temp;
for(int i=0,j=n-1;i<j;i++,j--)
{
temp = s[i];
s[i] = s[j];
s[j] = temp;
}
}
};
2.2 【数学】整数反转
https://leetcode.cn/problems/reverse-integer/
这道题目考察到了int的范围,题目中给出的范围限制刚好是int所表示的整数范围,所以一开始先定义一个long型遍历,按照取余之后计算反转后的整数,如果这个整数的类型转为int之后与原来不一致了,说明这个整数的范围超出了int,否则没有超出。
class Solution {
public:
int reverse(int x) {
long ans = 0;
while(x!=0)
{
int temp = x % 10;
ans = ans*10 + temp;
x /= 10;
}
if(int(ans)==ans) return ans;
else return 0;
}
};
2.3 【哈希表】字符串中的第一个唯一字符
https://leetcode.cn/problems/first-unique-character-in-a-string/
利用哈希表,如果遇到重复的字符,就把这个字符在哈希表中的value变为-1,否则存储index,最后遍历哈希表,输出第一个不为-1的index,否则返回-1。
class Solution {
public:
int firstUniqChar(string s) {
unordered_map<char,int> hash_table;
int n = s.size();
for(int i=n-1;i>=0;i--)
{
char temp = s[i];
if(hash_table.count(temp)) hash_table[temp] = -1;
else hash_table[temp] = i;
}
int m = hash_table.size();
for(auto x:hash_table)
{
if(x.second != -1) return x.second;
}
return -1;
}
};
2.4 【哈希表】有效的字母异位词
https://leetcode.cn/problems/valid-anagram/
分别对两个字符串构造哈希表,最后比较每个位置字符数量是否相等。
class Solution {
public:
bool isAnagram(string s, string t) {
int s_num[26]={
0}, t_num[26]={
0};
for(int i=0;i<s.size();i++)
{
s_num[s[i]-'a']++;
}
for(int j=0;j<t.size();j++)
{
t_num[t[j]-'a']++;
}
for(int k=0;k<26;k++)
{
if(s_num[k]!=t_num[k]) return false;
}
return true;
}
};
2.5 【双指针】验证回文串
https://leetcode.cn/problems/valid-palindrome/
使用双指针进行解题,但是在遍历的时候要注意判断一个地方,不只是’a’-'A’的值是32,‘P’-'0’的值也是32,所以在判断是否是同一大小写的时候加入两者是否都是字母的判断。
class Solution {
public:
bool isPalindrome(string s) {
int n = s.size();
if(n<=1) return true;
for(int i=0,j=n-1;i<j;i++,j--)
{
while(!isalnum(s[i]) && i<j)
{
i++;
}
while(!isalnum(s[j]) && i<j)
{
j--;
}
if(s[i]!=s[j] && (s[i]<'A' || s[j]<'A' || abs(s[i]-s[j])!=32)) return false;
}
return true;
}
};
2.6 【暴力】字符串转换整数(atoi)
https://leetcode.cn/problems/string-to-integer-atoi/
这道题目很恶心,让我感觉总是根据需求在不断的改代码。
首先遍历字符串时候,找出这个数是正是负,之后对于找到的位置从后开始输出完整数字,这里建议大家认真读题,不是说后面有数字都要输出,而是紧接着你找到的可以判断符合的这一个字符往后遍历,如果没有整数就直接输出0。
下面记录下我遇到的两个坑
“20000000000000000000"和” 0000000000012345678"
第一个让我加了判断是否超过int的位数的判断,第二个让我加入了要从第一个不为零的数字开始判断是否超过int的位数判断。
class Solution {
public:
int myAtoi(string s) {
int n = s.size();
int i = 0;
long ans = 0;
int ans_size = 0;
bool flag = true;
while(i<n)
{
if(s[i]=='-')
{
flag = false;
i++;
break;
}
else if(s[i]=='+')
{
i++;
break;
}
else if(s[i]!=' ')
{
break;
}
i++;
}
while(i<n)
{
if(s[i]=='0') i++;
else break;
}
for(;i<n;i++)
{
if(s[i]<'0' || s[i]>'9') break;
else
{
int x = s[i] - '0';
ans = ans * 10 + x;
ans_size++;
if(ans_size > 10) break;
}
}
if(!flag) ans = -ans;
if(int(ans)==ans) return ans;
else
{
if(flag) return pow(2,31)-1;
else return -pow(2,31);
}
}
};
2.7 【双指针】 找出字符串中第一个匹配项的下标
https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/
其实这道题目最好的解决办法是kmp,但是可以自己练习一下普通的双指针查找,我实现的是没有回溯的双指针解法,也就是如果碰到不匹配的情况haystack的index是向后移动一位,后续会改进一下看看kmp怎么实现。
class Solution {
public:
int strStr(string haystack, string needle) {
int h_size = haystack.size();
int n_size = needle.size();
int n_index = 0;
int ans = -1;
for(int i=0;i<h_size;i++)
{
if(haystack[i] == needle[n_index])
{
ans = i;
for(int j=i;j<h_size,n_index<n_size;j++,n_index++)
{
if(haystack[j]!=needle[n_index])
{
n_index = 0;
ans = -1;
break;
}
}
}
}
return ans;
}
};
2.8 【暴力】外观数列
https://leetcode.cn/problems/count-and-say/
暴力解决,注意观察数列特征,尤其是字符串的最后一位判断是否和前一位相同,也决定了要插入的数字是单独一个,还是和前面几个相同的数字同时插入,比如最后结尾是“222”就要插入“32”,而最后结尾是“223”就要插入“2213”。
class Solution {
public:
string countAndSay(int n) {
string ans = "11";
int x_num = 1;
if(n==1) return "1";
if(n==2) return "11";
for(int i=3;i<=n;i++)
{
string n_str;
for(int j=0;j<ans.size();j++)
{
if(j==ans.size()-1)
{
n_str.push_back(x_num + '0');
n_str.push_back(ans[j]);
x_num = 1;
}
else
{
if(ans[j]==ans[j+1]) x_num++;
else
{
n_str.push_back(x_num + '0');
n_str.push_back(ans[j]);
x_num = 1;
}
}
}
ans = n_str;
}
return ans;
}
};
2.8 【字典树】最长公共前缀
https://leetcode.cn/problems/longest-common-prefix/
这里因为是要找出最长公共前缀,所以对strs里面的每一个字符串采取纵向比较,按个比对,一旦出现纵向不一致,立即结束循环输出ans。
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
string ans = "";
for(int i=0;strs[0][i];i++)
{
for(int j=0;j<strs.size()-1;j++)
{
if(strs[j][i]!=strs[j+1][i]) return ans;
}
ans.push_back(strs[0][i]);
}
return ans;
}
};