leetcode 解题记录

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/HERO_CJN/article/details/82919340

一、数组

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.旋转数组

给定一个数组,将数组中的元素向右移动 个位置,其中 是非负数。

扫描二维码关注公众号,回复: 3719137 查看本文章

示例 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. 数字 1-9 在每一行只能出现一次。
  2. 数字 1-9 在每一列只能出现一次。
  3. 数字 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 的二维矩阵表示一个图像。

将图像顺时针旋转 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 和 的值均不超过 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 和 的值均不超过 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个节点

给定一个链表,删除链表的倒数第 个节点,并且返回链表的头结点。

示例:

给定一个链表: 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;
    }
};
//迭代

猜你喜欢

转载自blog.csdn.net/HERO_CJN/article/details/82919340