一、数组
724.寻找数组的中心索引
给定一个整数类型的数组 nums
,请编写一个能够返回数组“中心索引”的方法。
我们是这样定义数组中心索引的:数组中心索引的左侧所有元素相加的和等于右侧所有元素相加的和。
如果数组不存在中心索引,那么我们应该返回 -1。如果数组有多个中心索引,那么我们应该返回最靠近左边的那一个。
示例 1:
输入:
nums = [1, 7, 3, 6, 5, 6]
输出: 3
解释:
索引3 (nums[3] = 6) 的左侧数之和(1 + 7 + 3 = 11),与右侧数之和(5 + 6 = 11)相等。
同时, 3 也是第一个符合要求的中心索引。
示例 2:
输入:
nums = [1, 2, 3]
输出: -1
解释:
数组中不存在满足此条件的中心索引。
说明:
nums
的长度范围为[0, 10000]
。- 任何一个
nums[i]
将会是一个范围在[-1000, 1000]
的整数。
class Solution {
public:
int pivotIndex(vector<int>& nums) {
int i = 0, j = nums.size()-1;
int left = 0, right = 0;
while(i < j)
{
if(left < right)
left += nums[i++];
else
right += nums[j--];
}
if(left == right)
return i;
return -1;
}
};
747.至少是其他数字两倍的最大数
在一个给定的数组nums
中,总是存在一个最大元素 。
查找数组中的最大元素是否至少是数组中每个其他数字的两倍。
如果是,则返回最大元素的索引,否则返回-1。
示例 1:
输入: nums = [3, 6, 1, 0]
输出: 1
解释: 6是最大的整数, 对于数组中的其他整数,
6大于数组中其他元素的两倍。6的索引是1, 所以我们返回1.
示例 2:
输入: nums = [1, 2, 3, 4]
输出: -1
解释: 4没有超过3的两倍大, 所以我们返回 -1.
提示:
nums
的长度范围在[1, 50]
.- 每个
nums[i]
的整数范围在[0, 99]
.
class Solution {
public:
int dominantIndex(vector<int>& nums) {
int n = nums.size();
if(n == 1)
return 0;
vector<int> temp(nums.begin(), nums.end());
sort(temp.begin(), temp.end());
if(temp[n-1] >= 2*temp[n-2])
{
for(int i = 0; i < n; i++)
if(nums[i] == temp[n-1])
return i;
}
return -1;
}
};
第二种解法,找数组最大的两个数,时间复杂度为O(n)
class Solution {
public:
int dominantIndex(vector<int>& nums) {
int n = nums.size();
if(n == 1)
return 0;
int max1 = 0, max2 = 0, index = -1;
for(int i = 0; i < n; i++)
{
if(nums[i] > max1)
{
index = i;
max2 = max1;
max1 = nums[i];
}
else if(nums[i] > max2)
max2 = nums[i];
}
if(max1 >= 2*max2)
return index;
return -1;
}
};
66.加一
给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。
最高位数字存放在数组的首位, 数组中每个元素只存储一个数字。
你可以假设除了整数 0 之外,这个整数不会以零开头。
示例 1:
输入: [1,2,3]
输出: [1,2,4]
解释: 输入数组表示数字 123。
示例 2:
输入: [4,3,2,1]
输出: [4,3,2,2]
解释: 输入数组表示数字 4321。
class Solution {
public:
vector<int> plusOne(vector<int>& digits) {
int carry = 1, sum = 0;
vector<int> ret;
for(int i = digits.size()-1; i >= 0 ; i--)
{
if(carry == 0)
return digits;
sum = carry + digits[i];
carry = sum/10;
digits[i] = sum%10;
sum = 0;
}
if(carry == 1)
digits.insert(digits.begin(), 1);
return digits;
}
};
二、二维数组
498.对角线遍历
给定一个含有 M x N 个元素的矩阵(M 行,N 列),请以对角线遍历的顺序返回这个矩阵中的所有元素,对角线遍历如下图所示。
示例:
输入:
[
[ 1, 2, 3 ],
[ 4, 5, 6 ],
[ 7, 8, 9 ]
]
输出: [1,2,4,7,5,3,6,8,9]
解释:
说明:
- 给定矩阵中的元素总数不会超过 100000 。
解题思路:
进行对角线遍历,移动的方向不再是水平或竖直方向,而是对角线方向,那么每移动一次,横纵坐标都要变化,向右上移动的话要坐标加上[-1, 1],向左下移动的话要坐标加上[1, -1],如何处理越界情况,越界后遍历的方向怎么变换。向右上和左下两个对角线方向遍历的时候都会有越界的可能,但是除了左下角和右上角的位置越界需要改变两个坐标之外,其余的越界只需要改变一个。那么我们就先判断要同时改变两个坐标的越界情况,即在右上角和左下角的位置。如果在右上角位置还要往右上走时,那么要移动到它下面的位置的,那么如果col超过了n-1的范围,那么col重置为n-1,并且row自增2,然后改变遍历的方向。同理如果row超过了m-1的范围,那么row重置为m-1,并且col自增2,然后改变遍历的方向。然后我们再来判断一般的越界情况,如果row小于0,那么row重置0,然后改变遍历的方向。同理如果col小于0,那么col重置0,然后改变遍历的方向。
对角线遍历
给定一个含有 M x N 个元素的矩阵(M 行,N 列),请以对角线遍历的顺序返回这个矩阵中的所有元素,对角线遍历如下图所示。
示例:
输入:
[
[ 1, 2, 3 ],
[ 4, 5, 6 ],
[ 7, 8, 9 ]
]
输出: [1,2,4,7,5,3,6,8,9]
解释:
说明:
给定矩阵中的元素总数不会超过 100000 。
54.螺旋矩阵
给定一个包含 m x n 个元素的矩阵(m 行, n 列),请按照顺时针螺旋顺序,返回矩阵中的所有元素。
示例 1:
输入:
[
[ 1, 2, 3 ],
[ 4, 5, 6 ],
[ 7, 8, 9 ]
]
输出: [1,2,3,6,9,8,7,4,5]
示例 2:
输入:
[
[1, 2, 3, 4],
[5, 6, 7, 8],
[9,10,11,12]
]
输出: [1,2,3,4,8,12,11,10,9,5,6,7]
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
vector<int> ret;
if(matrix.empty())
return ret;
int row = matrix.size(), col = matrix[0].size();
vector<vector<int>> visit(row, vector<int>(col, 0));
int flag = 1;
int x = 0, y = 0;
ret.push_back(matrix[x][y]);
visit[x][y] = 1;
while(flag)
{
flag = 0;
while(y + 1< col && visit[x][y+1] == 0)
{
ret.push_back(matrix[x][++y]);
visit[x][y] = 1;
flag = 1;
}
while(x + 1< row && visit[x+1][y] == 0)
{
ret.push_back(matrix[++x][y]);
visit[x][y] = 1;
flag = 1;
}
while(y > 0 && visit[x][y-1] == 0)
{
ret.push_back(matrix[x][--y]);
visit[x][y] = 1;
flag = 1;
}
while(x > 0 && visit[x-1][y] == 0)
{
ret.push_back(matrix[--x][y]);
visit[x][y] = 1;
flag = 1;
}
}
return ret;
}
};
118.杨辉三角
给定一个非负整数 numRows,生成杨辉三角的前 numRows 行。
在杨辉三角中,每个数是它左上方和右上方的数的和。
示例:
输入: 5
输出:
[
[1],
[1,1],
[1,2,1],
[1,3,3,1],
[1,4,6,4,1]
]
class Solution {
public:
vector<vector<int>> generate(int numRows) {
vector<vector<int>>ret;
if(numRows == 0)
return ret;
ret.resize(numRows);
for(int i = 0; i < numRows; i++)
ret[i].resize(i+1);
ret[0][0] = 1;
int i = 1;
while(i < numRows)
{
ret[i][0] = 1;
ret[i][i] = 1;
for(int j = 1; j < i; j++)
ret[i][j] = ret[i-1][j-1] + ret[i-1][j];
i++;
}
return ret;
}
};
三、字符串
67.二进制求和
给定两个二进制字符串,返回他们的和(用二进制表示)。
输入为非空字符串且只包含数字 1
和 0
。
示例 1:
输入: a = "11", b = "1"
输出: "100"
示例 2:
输入: a = "1010", b = "1011"
输出: "10101"
class Solution {
public:
string addBinary(string a, string b) {
string ret = "";
int x = a.size(), y = b.size();
int i = x-1, j = y-1;
int c = 0;
while(i >= 0 || j >= 0 || c > 0)
{
c += i >= 0 ? (a[i--] - '0') : 0;
c += j >= 0 ? (b[j--] - '0') : 0;
ret = char(c % 2 + '0') + ret;
c = c / 2;
}
return ret;
}
};
28.实现strStr()
实现 strStr() 函数。
给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。
示例 1:
输入: haystack = "hello", needle = "ll"
输出: 2
示例 2:
输入: haystack = "aaaaa", needle = "bba"
输出: -1
说明:
当 needle
是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。
对于本题而言,当 needle
是空字符串时我们应当返回 0 。这与C语言的 strstr() 以及 Java的 indexOf() 定义相符。
class Solution {
public:
int strStr(string haystack, string needle) {
int m = haystack.size(), n = needle.size();
if(n == 0)
return 0;
for(int i = 0; i <= m-n; i++)
{
if(haystack[i] == needle[0])
{
string temp = haystack.substr(i, n);
if(temp.compare(needle) == 0)
return i;
}
}
return -1;
}
};
14.最长公共前缀
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 ""
。
示例 1:
输入: ["flower","flow","flight"]
输出: "fl"
示例 2:
输入: ["dog","racecar","car"]
输出: ""
解释: 输入不存在公共前缀。
说明:
所有输入只包含小写字母 a-z
。
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
if(strs.empty())
return "";
char c;
for(int i = 0; i < strs[0].size(); i++)
{
c = strs[0][i];
for(int j = 1; j < strs.size(); j++)
{
if(i >= strs[j].size() || c != strs[j][i])
return strs[0].substr(0, i);
}
}
return strs[0];
}
};
四、双指针技巧
1.从两端向中间迭代数组,一个指针从始端开始,而另一个指针从末端开始。这种技巧经常在排序
数组中使用
561.数组拆分 I
给定长度为 2n 的数组, 你的任务是将这些数分成 n 对, 例如 (a1, b1), (a2, b2), ..., (an, bn) ,使得从1 到 n 的 min(ai, bi) 总和最大。
示例 1:
输入: [1,4,3,2]
输出: 4
解释: n 等于 2, 最大总和为 4 = min(1, 2) + min(3, 4).
提示:
- n 是正整数,范围在 [1, 10000].
- 数组中的元素范围在 [-10000, 10000].
第一种暴力求解,时间复杂度O(n2)
class Solution {
public:
int arrayPairSum(vector<int>& nums) {
sort(nums.begin(), nums.end());
int ret = 0;
for(int i = 0; i < nums.size(); i = i+2)
ret += nums[i];
return ret;
}
};
第二种使用hash表,空间换时间。时间和空间复杂度都是O(n)。
class Solution {
public:
int arrayPairSum(vector<int>& nums) {
int hashtable[20001]={0};
int n = nums.size();
for(int i = 0; i < n;i++)
{
int temp = nums[i] + 10000;
hashtable[temp]++;
}
int j = 0;
int flag = 0, ret = 0;
while(j < 20000)
{
if(flag == 0 && hashtable[j] > 0)
{
ret += j - 10000;
hashtable[j]--;
flag = 1;
}
else if(flag == 1 && hashtable[j] > 0)
{
hashtable[j]--;
flag = 0;
}
else
j++;
}
return ret;
}
};
167.两数之和 II - 输入有序数组
给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。
函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。
说明:
- 返回的下标值(index1 和 index2)不是从零开始的。
- 你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
示例:
输入: numbers = [2, 7, 11, 15], target = 9
输出: [1,2]
解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
int i = 0, j = numbers.size()-1;
vector<int> ret;
while(i < j)
{
if(target == numbers[i] + numbers[j])
{
ret.push_back(i+1);
ret.push_back(j+1);
i++;
j--;
}
else if(target > numbers[i] + numbers[j])
i++;
else
j--;
}
return ret;
}
};
2.同时有一个慢指针和一个快指针
27.移除元素
给定一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例 1:
给定 nums = [3,2,2,3], val = 3,
函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。
你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 nums = [0,1,2,2,3,0,4,2], val = 2,
函数应该返回新的长度为5,并且 nums 中的前五个元素为[0,1,3,0,4]注意这五个元素可为任意顺序。
你不需要考虑数组中超出新长度后面的元素。
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int k = 0, n = nums.size();
for(int i = 0; i < n;i++)
{
if(nums[i] != val)
nums[k++] = nums[i];
}
return k;
}
};
485.最大连续1的个数
给定一个二进制数组, 计算其中最大连续1的个数。
示例 1:
输入: [1,1,0,1,1,1]
输出: 3
解释: 开头的两位和最后的三位都是连续1,所以最大连续1的个数是 3.
注意:
- 输入的数组只包含
0
和1
。 - 输入数组的长度是正整数,且不超过 10,000。
class Solution {
public:
int findMaxConsecutiveOnes(vector<int>& nums) {
int first = -1, second = -1;
int ret = 0;
for(int i = 0; i < nums.size();i++)
{
if(nums[i] == 1)
{
second = i;
if(second - first > ret)
ret = second -first;
}
else
first = i;
}
return ret;
}
};
209.长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组。如果不存在符合条件的连续子数组,返回 0。
示例:
输入:s = 7, nums = [2,3,1,2,4,3]
输出: 2 解释: 子数组[4,3]
是该条件下的长度最小的连续子数组。
进阶:
如果你已经完成了O(n) 时间复杂度的解法, 请尝试 O(n log n) 时间复杂度的解法。
第一种暴力求解,时间复杂度O(n2)
class Solution {
public:
int minSubArrayLen(int s, vector<int>& nums) {
int ret = INT_MAX;
int i, j;
for(i = 0; i < nums.size();i++)
{
int sum = 0;
for(j = i; j < nums.size(); j++)
{
sum += nums[j];
if(sum >= s)
{
ret = min(ret, j-i+1);
break;
}
}
}
if(ret == INT_MAX)
return 0;
return ret;
}
};
第二种方法使用快慢指针,时间复杂度O(n)
class Solution {
public:
int minSubArrayLen(int s, vector<int>& nums) {
if(nums.empty())
return 0;
int ret = INT_MAX;
int i = 0, j = -1, sum = 0;
int n = nums.size();
sum += nums[0];
while(i < n)
{
if(sum < s)
{
++i;
if(i < n)
sum += nums[i];
}
else
{
ret = min(ret, i-j);
if(ret == 1)
return 1;
j++;
sum -= nums[j];
}
}
if(ret == INT_MAX)
return 0;
return ret;
}
};
五、小结
189.旋转数组
给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。
示例 1:
输入:[1,2,3,4,5,6,7]
和 k = 3 输出:[5,6,7,1,2,3,4]
解释: 向右旋转 1 步:[7,1,2,3,4,5,6]
向右旋转 2 步:[6,7,1,2,3,4,5]
向右旋转 3 步:[5,6,7,1,2,3,4]
示例 2:
输入: [-1,-100,3,99]
和 k = 2
输出: [3,99,-1,-100]
解释:
向右旋转 1 步: [99,-1,-100,3]
向右旋转 2 步: [3,99,-1,-100]
说明:
- 尽可能想出更多的解决方案,至少有三种不同的方法可以解决这个问题。
- 要求使用空间复杂度为 O(1) 的原地算法。
第一种方法使用O(n)的空间,时间复杂度O(n)
class Solution {
public:
void rotate(vector<int>& nums, int k) {
int n = nums.size();
if(n == 0 || (k % nums.size()) == 0)
return;
vector<int> temp = nums;
for(int i = 0; i < n;i++)
{
nums[(i+k)%n] = temp[i];
}
return;
}
};
第二种方法使用reverse,三次颠倒,空间复杂度O(1)
class Solution {
public:
void rotate(vector<int>& nums, int k) {
if(nums.empty())
return;
int n = nums.size();
k = k % n;
if(k == 0)
return ;
reverse(nums.begin(), nums.end());
reverse(nums.begin(), nums.begin()+k);
reverse(nums.begin()+k, nums.end());
return;
}
};
第三种方法,删除和添加vector的元素,但是这样比较耗时。
class Solution {
public:
void rotate(vector<int>& nums, int k) {
int n = nums.size();
if(n == 0 || (k % nums.size()) == 0)
return;
k = k % n;
for(int i = 0; i <n - k;i++)
{
nums.push_back(nums[0]);
nums.erase(nums.begin());
}
return;
}
};
119.杨辉三角 II
给定一个非负索引 k,其中 k ≤ 33,返回杨辉三角的第 k 行。
在杨辉三角中,每个数是它左上方和右上方的数的和。
示例:
输入: 3
输出: [1,3,3,1]
进阶:
你可以优化你的算法到 O(k) 空间复杂度吗?
解题思路:
杨辉三角可以看成是一行行的数组
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
每一个数组都比上一个数组多一个数,第一位和最后一位都是1, 其余数字都是上一个数组对应位置和前一位置的数字的和, a[i][j] = a[i-1][j] + a[i-1][j-1]
在O(k)的空间复杂度的限制下,在长度为k的数组内部,从杨辉三角的第一行开始依次计算到第k行的最终结果。
j从i开始,是因为第i行共有i+1个数字,从后往前计算,避免了第i-1行计算结果被覆盖丢失。
class Solution {
public:
vector<int> getRow(int rowIndex) {
vector<int> ret(rowIndex + 1, 0);
ret[0] = 1;
for(int i = 1; i < rowIndex + 1; i++)
for(int j = i; j >= 1;j--)
{
ret[j] = ret[j] + ret[j-1];
}
return ret;
}
};
翻转字符串里的单词
给定一个字符串,逐个翻转字符串中的每个单词。
示例 1:
输入:the sky is blue
输出:blue is sky the
示例 2:
输入: " hello world! " 输出: "world! hello" 解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
示例 3:
输入: "a good example" 输出: "example good a" 解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
说明:
- 无空格字符构成一个单词。
- 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
- 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
class Solution {
public:
string reverseWords(string s) {
if(s.empty())
return s;
int i = 0, j, k = 0;
int flag = 0;
while( i < s.length())
{
if(s[i] != ' ')
break;
i++;
}
if(i == s.length())
return "";
for( ; i < s.length();i++)
{
if(s[i] != ' '&& flag == 0)
{
j = i;
flag = 1;
}
if(s[i] == ' '&& flag == 1)
{
string temp = s.substr(j, i-j);
reverse(temp.begin(), temp.end());
int n = i - j;
int count = 0;
while(count < n)
s[k++] = temp[count++];
s[k++] = ' ';
flag = 0;
}
}
if(flag == 1)
{
string temp = s.substr(j, i-j);
reverse(temp.begin(), temp.end());
int n = i - j;
int count = 0;
while(count < n)
s[k++] = temp[count++];
s[k++] = ' ';
}
reverse(s.begin(), s.begin() + k - 1);
return s.substr(0, k - 1);
}
};
557.反转字符串中的单词 III
给定一个字符串,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。
示例 1:
输入: "Let's take LeetCode contest" 输出: "s'teL ekat edoCteeL tsetnoc"
注意:在字符串中,每个单词由单个空格分隔,并且字符串中不会有任何额外的空格。
class Solution {
public:
string reverseWords(string s) {
int cur = 0, last = 0;
string temp;
while(cur < s.size())
{
if(s[cur] == ' ')
{
temp = s.substr(last, cur-last);
reverse(temp.begin(), temp.end());
int count = last, i = 0;
while(count < cur)
s[count++] = temp[i++];
last = cur+1;
}
cur++;
}
temp = s.substr(last, cur-last);
reverse(temp.begin(), temp.end());
int count = last, i = 0;
while(count < cur)
s[count++] = temp[i++];
return s;
}
};
26.删除排序数组中的重复项
给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
示例 1:
给定数组 nums = [1,1,2], 函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 [1,2] 你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 nums = [0,0,1,1,1,2,2,3,3,4], 函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 [0,1,2,3,4] 你不需要考虑数组中超出新长度后面的元素。
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int n = nums.size();
if(n < 2)
return n;
int last = 0;
for(int cur= 1; cur < n; cur++)
{
if(nums[cur] != nums[cur-1])
nums[++last] = nums[cur];
}
return last+1;
}
};
283.移动零
给定一个数组 nums
,编写一个函数将所有 0
移动到数组的末尾,同时保持非零元素的相对顺序。
示例:
输入:[0,1,0,3,12]
输出:[1,3,12,0,0]
说明:
- 必须在原数组上操作,不能拷贝额外的数组。
- 尽量减少操作次数。
class Solution {
public:
void moveZeroes(vector<int>& nums) {
if(nums.empty())
return;
int j = 0;
for(int i = 0;i < nums.size();i++)
{
if(nums[i] != 0)
nums[j++] = nums[i];
}
while(j <nums.size())
nums[j++] = 0;
return ;
}
};
class Solution {
public:
void moveZeroes(vector<int>& nums) {
if(nums.empty())
return;
int last = 0, cur = 0;
while(cur < nums.size())
{
if(nums[cur] != 0)
{
swap(nums[last], nums[cur]);
last++;
}
cur++;
}
return ;
}
};