一、数组
1、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) {
if (nums.empty()) return 0;
int j = 0, n = nums.size();
for (int i = 0; i < n; ++i) {
if (nums[i] != nums[j]) nums[++j] = nums[i];
}
return j + 1;
}
};
//么这道题的解题思路是,我们使用快慢指针来记录遍历的坐标,最开始时两个指针都指向第一个数字,如果两
//个指针指的数字相同,则快指针向前走一步,如果不同,则两个指针都向前走一步,这样当快指针走完整个数
//组后,慢指针当前的坐标加1就是数组中不同数字的个数
2、122.买卖股票的最佳时机 II
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
示例 2:
输入: [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
示例 3:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int res = 0, n = prices.size();
for (int i = 0; i < n - 1; ++i) {
if (prices[i] < prices[i + 1]) {
res += prices[i + 1] - prices[i];
}
}
return res;
}
};
//如果当前价格比之前价格高,则把差值加入利润中,因为我们可以昨天买入,今日卖出,若明日价更高的话,还
//可以今日买入,明日再抛出。以此类推,遍历完整个数组后即可求得最大利润。
3、 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) 的原地算法。
class Solution {
public:
void rotate(vector<int>& nums, int k) {
if (nums.empty() || (k %= nums.size()) == 0) return;
int n = nums.size();
reverse(nums.begin(), nums.begin() + n - k);
reverse(nums.begin() + n - k, nums.end());
reverse(nums.begin(), nums.end());
}
};
//1 2 3 4 5 6 7
//4 3 2 1 5 6 7
//4 3 2 1 7 6 5
//5 6 7 1 2 3 4
4、 217.存在重复
给定一个整数数组,判断是否存在重复元素。
如果任何值在数组中出现至少两次,函数返回 true。如果数组中每个元素都不相同,则返回 false。
示例 1:
输入: [1,2,3,1]
输出: true
示例 2:
输入: [1,2,3,4]
输出: false
示例 3:
输入: [1,1,1,3,3,4,3,2,4,2]
输出: true
//使用一个哈希表,遍历整个数组,如果哈希表里存在,返回false,如果不存在,则将其放入哈希表中
class Solution {
public:
bool containsDuplicate(vector<int>& nums) {
unordered_map<int, int> m;
for (int i = 0; i < nums.size(); ++i) {
if (m.find(nums[i]) != m.end()) return true;
++m[nums[i]];
}
return false;
}
};
//先将数组排个序,然后再比较相邻两个数字是否相等,时间复杂度取决于排序方法
class Solution {
public:
bool containsDuplicate(vector<int>& nums) {
sort(nums.begin(), nums.end());
for (int i = 1; i < nums.size(); ++i) {
if (nums[i] == nums[i - 1]) return true;
}
return false;
}
};
5、136.只出现一次的数字
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
输入: [2,2,1]
输出: 1
示例 2:
输入: [4,1,2,1,2]
输出: 4
class Solution {
public:
int singleNumber(vector<int>& nums) {
int res = 0;
for (auto num : nums) res ^= num;
return res;
}
};
//我们把数组中所有的数字都异或起来,则每对相同的数字都会得0,然后最后剩下来的数字就是那个只有1次的
//数字
6、350.两个数组的交集 II
给定两个数组,编写一个函数来计算它们的交集。
示例 1:
输入: nums1 = [1,2,2,1], nums2 = [2,2]
输出: [2,2]
示例 2:
输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出: [4,9]
说明:
- 输出结果中每个元素出现的次数,应与元素在两个数组中出现的次数一致。
- 我们可以不考虑输出结果的顺序。
进阶:
- 如果给定的数组已经排好序呢?你将如何优化你的算法?
- 如果 nums1 的大小比 nums2 小很多,哪种方法更优?
- 如果 nums2 的元素存储在磁盘上,磁盘内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?
//这道题我们用哈希表来建立nums1中字符和其出现个数之间的映射, 然后遍历nums2数组,如果当前字符在哈
//希表中的个数大于0,则将此字符加入结果res中,然后哈希表的对应值自减1
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
unordered_map<int, int> m;
vector<int> res;
for (auto a : nums1) ++m[a];
for (auto a : nums2) {
if (m[a]-- > 0) res.push_back(a);
}
return res;
}
};
//这种方法先给两个数组排序,然后用两个指针分别指向两个数组的起始位置,如果两个指针指的数字相等,则
//存入结果中,两个指针均自增1,如果第一个指针指的数字大,则第二个指针自增1,反之亦然
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
vector<int> res;
int i = 0, j = 0;
sort(nums1.begin(), nums1.end());
sort(nums2.begin(), nums2.end());
while (i < nums1.size() && j < nums2.size()) {
if (nums1[i] == nums2[j]) {
res.push_back(nums1[i]);
++i; ++j;
} else if (nums1[i] > nums2[j]) {
++j;
} else {
++i;
}
}
return res;
}
};
10、36.有效的数独
判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。
- 数字
1-9
在每一行只能出现一次。 - 数字
1-9
在每一列只能出现一次。 - 数字
1-9
在每一个以粗实线分隔的3x3
宫内只能出现一次。
上图是一个部分填充的有效的数独。
数独部分空格内已填入了数字,空白格用 '.'
表示。
示例 1:
输入:
[
["5","3",".",".","7",".",".",".","."],
["6",".",".","1","9","5",".",".","."],
[".","9","8",".",".",".",".","6","."],
["8",".",".",".","6",".",".",".","3"],
["4",".",".","8",".","3",".",".","1"],
["7",".",".",".","2",".",".",".","6"],
[".","6",".",".",".",".","2","8","."],
[".",".",".","4","1","9",".",".","5"],
[".",".",".",".","8",".",".","7","9"]
]
输出: true
示例 2:
输入:
[
["8","3",".",".","7",".",".",".","."],
["6",".",".","1","9","5",".",".","."],
[".","9","8",".",".",".",".","6","."],
["8",".",".",".","6",".",".",".","3"],
["4",".",".","8",".","3",".",".","1"],
["7",".",".",".","2",".",".",".","6"],
[".","6",".",".",".",".","2","8","."],
[".",".",".","4","1","9",".",".","5"],
[".",".",".",".","8",".",".","7","9"]
]
输出: false
解释: 除了第一行的第一个数字从 5 改为 8 以外,空格内其他数字均与 示例1 相同。
但由于位于左上角的 3x3 宫内有两个 8 存在, 因此这个数独是无效的。
说明:
- 一个有效的数独(部分已被填充)不一定是可解的。
- 只需要根据以上规则,验证已经填入的数字是否有效即可。
- 给定数独序列只包含数字
1-9
和字符'.'
。 - 给定数独永远是
9x9
形式的。
class Solution {
public:
bool isValidSudoku(vector<vector<char>>& board) {
int row_num = board.size();
int col_num = board[0].size();
vector<vector<bool>> row(row_num, vector<bool>(col_num,false));
vector<vector<bool>> col(row_num, vector<bool>(col_num,false));
vector<vector<bool>> cell(row_num, vector<bool>(col_num,false));
for(int i = 0; i < row_num; ++i){
for(int j = 0; j < col_num; ++j){
if(board[i][j] >= '1' && board[i][j] <= '9'){
int num = board[i][j] - '1';
if(row[i][num] || col[num][j] || cell[3*(i/3)+j/3][num])
return false;
else{
row[i][num] = true;
col[num][j] = true;
cell[3*(i/3)+j/3][num] = true;
}
}
}
}
return true;
}
};
//row 某行中是否出现过数字X
//某列中是否出现过数字X
//某个3x3方阵中是否出现过数字X
11、48.旋转图像
给定一个 n × n 的二维矩阵表示一个图像。
将图像顺时针旋转 90 度。
说明:
你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。
示例 1:
给定 matrix =
[
[1,2,3],
[4,5,6],
[7,8,9]
],
原地旋转输入矩阵,使其变为:
[
[7,4,1],
[8,5,2],
[9,6,3]
]
示例 2:
给定 matrix =
[
[ 5, 1, 9,11],
[ 2, 4, 8,10],
[13, 3, 6, 7],
[15,14,12,16]
],
原地旋转输入矩阵,使其变为:
[
[15,13, 2, 5],
[14, 3, 4, 1],
[12, 6, 8, 9],
[16, 7,10,11]
]
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
for(int i = 0; i < matrix.size(); ++i){
for(int j = i+1; j < matrix.size(); ++j){
swap(matrix[i][j], matrix[j][i]);
}
reverse(matrix[i].begin(), matrix[i].end());
}
}
};
//先做转置,再做每一行的反转
动态规划
1、53. 最大子序和
给定一个整数数组 nums
,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
进阶:
如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int sub_sum[nums.size()] = {0};
int sum = sub_sum[0] = nums[0];
for(int i = 1; i < nums.size(); ++i){
if(sub_sum[i-1] < 0)
sub_sum[i] = nums[i];
else
sub_sum[i] = sub_sum[i-1] + nums[i];
sum = max(sum, sub_sum[i]);
}
return sum;
}
};
//f(n) = a[n] + max(f(n-1), 0)
2、62. 不同路径
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
问总共有多少条不同的路径?
例如,上图是一个7 x 3 的网格。有多少可能的路径?
说明:m 和 n 的值均不超过 100。
示例 1:
输入: m = 3, n = 2
输出: 3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向右 -> 向下
2. 向右 -> 向下 -> 向右
3. 向下 -> 向右 -> 向右
示例 2:
输入: m = 7, n = 3
输出: 28
class Solution {
public:
int uniquePaths(int m, int n) {
vector<vector<int> > dp(m, vector<int>(n,1));
for(int i = 1; i < m; ++i){
for(int j = 1; j < n; ++j){
dp[i][j] = dp[i][j-1] + dp[i-1][j];
}
}
return dp[m-1][n-1];
}
};
//1 只能向下或者向右走。所以当在i=0 或者 j = 0时,等于1
//2 dp[i][j] = dp[i-1][j] + dp[i][j-1];
3、63. 不同路径 II
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?
网格中的障碍物和空位置分别用 1
和 0
来表示。
说明:m 和 n 的值均不超过 100。
示例 1:
输入:
[
[0,0,0],
[0,1,0],
[0,0,0]
]
输出: 2
解释:
3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:
1. 向右 -> 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右 -> 向右
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
int m = obstacleGrid.size();
int n = 0;
if(!obstacleGrid.empty())
n = obstacleGrid[0].size();
vector<vector<int> > dp(m, vector<int>(n,1));
if(!obstacleGrid.empty()){
for(int j = 0; j < obstacleGrid[0].size(); ++j){
if(obstacleGrid[0][j]){
for(int i = j; i < obstacleGrid[0].size(); ++i){
dp[0][i] = 0;
}
}
}
}
for(int i = 0; i < obstacleGrid.size(); ++i){
if(obstacleGrid[i][0]){
for(int j = i; j < obstacleGrid.size(); ++j){
dp[j][0] = 0;
}
}
}
for(int i = 1; i < m; ++i){
for(int j = 1; j < n; ++j){
if(obstacleGrid[i][j] == 0)
dp[i][j] = dp[i][j-1] + dp[i-1][j];
else
dp[i][j] = 0;
}
}
return dp[m-1][n-1];
}
};
//1 只能向下或者向右走。所以当在i=0 或者 j = 0时,等于1 ;如果当前网格的值为1,到此网格和此网格后面的路径数为0
//2 dp[i][j] = dp[i-1][j] + dp[i][j-1];
//3 如果当前网格的值为1,那么,到此网格的路径数归0
二、字符串
1、344.反转字符串
编写一个函数,其作用是将输入的字符串反转过来。
示例 1:
输入: "hello"
输出: "olleh"
示例 2:
输入: "A man, a plan, a canal: Panama"
输出: "amanaP :lanac a ,nalp a ,nam A"
class Solution {
public:
string reverseString(string s) {
int head = 0, tail = s.size()-1;
while(tail > head){
swap(s[tail], s[head]);
++head;
--tail;
}
return s;
}
};
//直接从两头往中间走,同时交换两边的字符即可
fast2、7.颠倒整数
给定一个 32 位有符号整数,将整数中的数字进行反转。
示例 1:
输入: 123
输出: 321
示例 2:
输入: -123
输出: -321
示例 3:
输入: 120
输出: 21
注意:
假设我们的环境只能存储 32 位有符号整数,其数值范围是 [−231, 231 − 1]。根据这个假设,如果反转后的整数溢出,则返回 0。
class Solution {
public:
int reverse(int x) {
long res = 0;
while(x != 0){
res = res*10 + x%10;
x /= 10;
}
if(res > INT_MAX || res < INT_MIN) return 0;
return res;
}
};
3、387.字符串中的第一个唯一字符
给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。
案例:
s = "leetcode"
返回 0.
s = "loveleetcode",
返回 2.
class Solution {
public:
int firstUniqChar(string s) {
unordered_map<char,int> m;
for(auto c : s)
++m[c];
for(int i = 0; i < s.size(); ++i){
if(m[s[i]] == 1)
return i;
}
return -1;
}
};
//用哈希表建立每个字符和其出现次数的映射,然后按顺序遍历字符串,找到第一个出现次数为1的字符,返回其位置即可
4、242.有效的字母异位词
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的一个字母异位词。
示例 1:
输入: s = "anagram", t = "nagaram"
输出: true
示例 2:
输入: s = "rat", t = "car"
输出: false
说明:
你可以假设字符串只包含小写字母。
进阶:
如果输入字符串包含 unicode 字符怎么办?你能否调整你的解法来应对这种情况?
class Solution {
public:
bool isAnagram(string s, string t) {
if (s.size() != t.size()) return false;
unordered_map<char, int> m;
for(auto c : s)
++m[c];
for(auto c : t){
if(--m[c] < 0) return false;
}
return true;
}
};
class Solution {
public:
bool isAnagram(string s, string t) {
if (s.size() != t.size()) return false;
int m[26] = {0};
for (int i = 0; i < s.size(); ++i) ++m[s[i] - 'a'];
for (int i = 0; i < t.size(); ++i) {
if (--m[t[i] - 'a'] < 0) return false;
}
return true;
}
};
//使用哈希表映射,
//用一个数组来代替哈希表
5、125.验证回文字符串
给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。
说明:本题中,我们将空字符串定义为有效的回文串。
示例 1:
输入: "A man, a plan, a canal: Panama"
输出: true
示例 2:
输入: "race a car"
输出: false
class Solution {
public:
bool isPalindrome(string s) {
int left = 0;
int right = s.size()-1;
while(left < right){
if(!is_alpha_num(s[left]))
++left;
else if(!is_alpha_num(s[right]))
--right;
else if((s[left]-'a'+32)%32 != (s[right]-'a'+32)%32)
return false;
else{
++left;
--right;
}
}
return true;
}
bool is_alpha_num(char c){
if(c >= 'a' && c <= 'z') return true;
else if(c >= 'A' && c <= 'Z') return true;
else if(c >= '0' && c <= '9') return true;
else return false;
}
};
7、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) {
bool state = false;
if(needle.size() > haystack.size())
return -1;
if(needle.empty())
return 0;
for(int i = 0; i < haystack.size(); ++i){
if(needle.size() > haystack.size()-i)
return -1;
for(int j = 0; j < needle.size(); ++j){
if(needle[j] != haystack[i+j])
break;
if(j == needle.size()-1)
return i;
}
}
return -1;
}
};
9、14.最长公共前缀
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 ""
。
示例 1:
输入: ["flower","flow","flight"]
输出: "fl"
示例 2:
输入: ["dog","racecar","car"]
输出: ""
解释: 输入不存在公共前缀。
说明:
所有输入只包含小写字母 a-z
。
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
if(strs.empty())
return "";
for(int i = 0; i < strs[0].size(); ++i){
for(int j = 0; j < strs.size()-1; ++j){
if(strs[j][i] != strs[j+1][i] || strs[j].size() <= i || strs[j+1].size() <= i){
return strs[0].substr(0,i);
}
}
}
return strs[0];
}
};
三、链表
1、237.删除链表中的节点
请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点,你将只被给定要求被删除的节点。
现有一个链表 -- head = [4,5,1,9],它可以表示为:
4 -> 5 -> 1 -> 9
示例 1:
输入: head = [4,5,1,9], node = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
示例 2:
输入: head = [4,5,1,9], node = 1
输出: [4,5,9]
解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.
说明:
- 链表至少包含两个节点。
- 链表中所有节点的值都是唯一的。
- 给定的节点为非末尾节点并且一定是链表中的一个有效节点。
- 不要从你的函数中返回任何结果。
/**
* 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) {
node->val = node->next->val;
node->next = node->next->next;
}
};
//由于没有办法得到node的前节点,
//我们只能通过将下一个节点的值复制到当前节点node,然后移除node的下一个节点来达到目的。
2、19. 删除链表的倒数第N个节点
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
示例:
给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.
说明:
给定的 n 保证是有效的。
进阶:
你能尝试使用一趟扫描实现吗?
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
if(!head->next)
return NULL;
ListNode* pre = head, *cur = head;
for(int i = 0; i < n; ++i){
cur = cur->next;
}
if(!cur)
return head->next;
while(cur->next){
pre = pre->next;
cur = cur->next;
}
pre->next = pre->next->next;
return head;
}
};
//我们需要用两个指针来帮助我们解题,pre和cur指针。
//首先cur指针先向前走N步,如果此时cur指向空,说明N为链表的长度,则需要移除的为首元素,那么此时我们返回head->next即可,
//如果cur存在,我们再继续往下走,此时pre指针也跟着走,直到cur为最后一个元素时停止,
//此时pre指向要移除元素的前一个元素,我们再修改指针跳过需要移除的元素即可。
3、206.反转链表
反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
进阶:
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
// class Solution {
// public:
// ListNode* reverseList(ListNode* head) {
// if(!head || !head->next)
// return head;
// ListNode* cur = head;
// ListNode* dummy = new ListNode(-1);
// dummy->next = head;
// while(cur->next){
// ListNode* tmp = cur->next;
// cur->next = tmp->next;
// tmp->next = dummy->next;
// dummy->next = tmp;
// }
// return dummy->next;
// }
// };
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if(!head || !head->next)
return head;
ListNode* res = reverseList(head->next);
head->next->next = head;// 子链表的尾节点指向前一个节点
head->next = NULL;
return res;
}
};
//链表的问题注意画出草稿图,理清楚指针的操作步骤就可以了。
//写递归程序最重要是把调用函数当做已经执行完了返回了值,然后需要做的后续工作,不用管递归的函数是怎样做的。
4、21.合并两个有序链表
将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
//递归
// class Solution {
// public:
// ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
// if (!l1) return l2;
// if (!l2) return l1;
// if (l1->val < l2->val) {
// l1->next = mergeTwoLists(l1->next, l2);
// return l1;
// } else {
// l2->next = mergeTwoLists(l1, l2->next);
// return l2;
// }
// }
// };
//迭代
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode* dummy = new ListNode(-1);
ListNode* cur = dummy;
while(l1 && l2){
if(l1->val < l2->val){
cur->next = l1;
l1 = l1->next;
}
else{
cur->next = l2;
l2 = l2->next;
}
cur = cur->next;
}
if(l1)
cur->next = l1;
else
cur->next = l2;
return dummy->next;
}
};
5、234.回文链表
请判断一个链表是否为回文链表。
示例 1:
输入: 1->2
输出: false
示例 2:
输入: 1->2->2->1
输出: true
进阶:
你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool isPalindrome(ListNode* head) {
if(!head || !head->next)
return true;
ListNode* slow = head, *fast = head;
while(fast->next && fast->next->next){
slow = slow->next;
fast = fast->next->next;
}
ListNode* dummy = new ListNode(-1);
ListNode* cur = slow->next;
dummy->next = cur;
while(cur->next){
ListNode* tmp = cur->next;
cur->next = tmp->next;
tmp->next = dummy->next;
dummy->next = tmp;
}
ListNode* pre = head, *back = dummy->next;
while(back)
{
if(back->val != pre->val){
return false;
}
pre = pre->next;
back = back->next;
}
return true;
}
};
//使用快慢指针找中点的原理是fast和slow两个指针,每次快指针走两步,慢指针走一步,等快指针走完时,慢指针的位置就是中点。
//在找到中点后,将后半段的链表翻转一下,这样我们就可以按照回文的顺序比较了。
6、141.环形链表
给定一个链表,判断链表中是否有环。
进阶:
你能否不使用额外空间解决此题?
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode *head) {
ListNode* slow = head, *fast = head;
while(fast && fast->next){
slow = slow->next;
fast = fast->next->next;
if(slow == fast){
return true;
}
}
return false;
}
};
//快慢指针
四、树
1、104.二叉树的最大深度
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7]
,
3
/ \
9 20
/ \
15 7
返回它的最大深度 3 。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
int maxDepth(TreeNode* root) {
if(!root)
return 0;
return 1+max(maxDepth(root->left), maxDepth(root->right));
}
};
//求二叉树的最大深度问题用到深度优先搜索DFS,递归的完美应用,跟求二叉树的最小深度问题原理相同。
2、98.验证二叉搜索树
给定一个二叉树,判断其是否是一个有效的二叉搜索树。
假设一个二叉搜索树具有如下特征:
- 节点的左子树只包含小于当前节点的数。
- 节点的右子树只包含大于当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
示例 1:
输入:
2
/ \
1 3
输出: true
示例 2:
输入:
5
/ \
1 4
/ \
3 6
输出: false
解释: 输入为: [5,1,4,null,null,3,6]。
根节点的值为 5 ,但是其右子节点值为 4 。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
//利用中序遍历结果为有序数列来做
// class Solution {
// public:
// bool isValidBST(TreeNode* root) {
// if(!root)
// return true;
// vector<int> val;
// inorder(root, val);
// for(int i = 0; i < val.size()-1; ++i){
// if(val[i] >= val[i+1]) return false;
// }
// return true;
// }
// void inorder(TreeNode* root, vector<int>& val){
// if(!root) return;
// inorder(root->left, val);
// val.push_back(root->val);
// inorder(root->right, val);
// }
// };
//利用它本身的性质来做,即左<根<右
class Solution {
public:
bool isValidBST(TreeNode* root) {
// if(!root)
// return true;
return isvalidBST(root, LONG_MIN, LONG_MAX);
}
bool isvalidBST(TreeNode* root, long left, long right){
if(!root)
return true;
if(root->val <= left || root->val >= right)
return false;
return isvalidBST(root->left, left, root->val) && isvalidBST(root->right, root->val, right);
}
};
3、101.对称二叉树
给定一个二叉树,检查它是否是镜像对称的。
例如,二叉树 [1,2,2,3,4,4,3]
是对称的。
1 / \ 2 2 / \ / \ 3 4 4 3
但是下面这个 [1,2,2,null,3,null,3]
则不是镜像对称的:
1 / \ 2 2 \ \ 3 3
说明:
如果你可以运用递归和迭代两种方法解决这个问题,会很加分。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
bool isSymmetric(TreeNode* root) {
if(!root)
return true;
return issymmetric(root->left, root->right);
}
bool issymmetric(TreeNode* left, TreeNode* right){
if(!left && !right)
return true;
if(!left && right || left &&! right || left->val != right->val)
return false;
return issymmetric(left->left,right->right) && issymmetric(left->right,right->left);
}
};
//递归
4、102.二叉树的层次遍历
给定一个二叉树,返回其按层次遍历的节点值。 (即逐层地,从左到右访问所有节点)。
例如:
给定二叉树: [3,9,20,null,null,15,7]
,
3 / \ 9 20 / \ 15 7
返回其层次遍历结果:
[ [3], [9,20], [15,7] ]
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> res;
if(!root)
return res;
queue<TreeNode*> q;
q.push(root);
while(!q.empty()){
int size = q.size();
vector<int> num;
for(int i = 0; i < size; ++i){
TreeNode* tmp = q.front();
q.pop();
num.push_back(tmp->val);
if(tmp->left) q.push(tmp->left);
if(tmp->right) q.push(tmp->right);
}
res.push_back(num);
}
return res;
}
};
//迭代