LeetCode题解之数组、链表

数组、链表

数组理论

二分法
双指针法
滑动窗口

二分法

81. 搜索旋转排序数组 II(☆☆☆)

已知存在一个按非降序排列的整数数组 nums ,数组中的值不必互不相同。

在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转 ,使数组变为 [nums[k], nums[k+1], …, nums[n-1], nums[0], nums[1], …, nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,4,4,5,6,6,7] 在下标 5 处经旋转后可能变为 [4,5,6,6,7,0,1,2,4,4] 。

给你 旋转后 的数组 nums 和一个整数 target ,请你编写一个函数来判断给定的目标值是否存在于数组中。如果 nums 中存在这个目标值 target ,则返回 true ,否则返回 false 。

class Solution {
public:
    /*
    如果 [l, mid - 1] 是有序数组,且 target 的大小满足 [nums[l],nums[mid]),
    则我们应该将搜索范围缩小至 [l, mid - 1],否则在 [mid + 1, r] 中寻找。
    如果 [mid, r] 是有序数组,且 target 的大小满足 (nums[mid+1],nums[r]],则我们应该将搜索范围缩小至 [mid + 1, r],否则在 [l, mid - 1] 中寻找。
    */


    //每次二分,左半部分和右半部分至少有一边是有序的,以此为条件可以分成两种情况:
    //1、左半边是有序的
    //(1) target落在左半边
    //(2) otherwise
    //2、右半边是有序的
    //(1) target落在右半边
    //(2) otherwise
    //综上所述,一共两种可能性,这两种情况各自又有两种可能性,代码如下:
    bool search(vector<int>& nums, int target) {
        int left = 0, right = nums.size()-1;

        while(left <= right) {

            //处理重复数字
            while(left < right && nums[left] == nums[left+1]) ++left;
            while(left < right && nums[right] == nums[right-1]) right--;

            int mid = left + (right - left)/2;

            if(nums[mid] == target) return true;
            if(nums[mid] >= nums[left]) {//target落在左半边
                if(target >= nums[left] && nums[mid] > target) right = mid - 1;
                else left = mid+1;
            } else {
                if(nums[right] >= target && nums[mid] < target) left = mid + 1;//target落在右边
                else right = mid - 1;
            }
        }
        return false;
    }
};

153. 寻找旋转排序数组中的最小值(☆☆☆)

已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:
若旋转 4 次,则可以得到 [4,5,6,7,0,1,2]
若旋转 7 次,则可以得到 [0,1,2,4,5,6,7]
注意,数组 [a[0], a[1], a[2], …, a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], …, a[n-2]] 。

给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。

class Solution {
public:
    int findMin(vector<int>& nums) {
        int left = 0, right = nums.size()-1;
        // 这里控制条件没取等号,取等号大多是为了在while中直return mid,不取等号就跳出while返回l的值。
        while(left < right) {
            int mid = left + (right - left)/2;
            if(nums[mid] > nums[right]) {
                // 中间数字大于右边数字,比如[3,4,5,1,2],则左侧是有序上升的,最小值在右侧
                left = mid + 1;
            } else {
                // 中间数字小于等于右边数字,比如[6,7,1,2,3,4,5],则右侧是有序上升的,最小值在左侧
                right = mid;
            }
        }
        return nums[left];
    }
};

154. 寻找旋转排序数组中的最小值 II(☆☆☆)

已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,4,4,5,6,7] 在变化后可能得到:
若旋转 4 次,则可以得到 [4,5,6,7,0,1,4]
若旋转 7 次,则可以得到 [0,1,4,4,5,6,7]
注意,数组 [a[0], a[1], a[2], …, a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], …, a[n-2]] 。

给你一个可能存在 重复 元素值的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。

class Solution {
public:
    int findMin(vector<int>& nums) {
        /*
        平均时间复杂度为 O(logn),而在最坏情况下,如果数组中的元素完全相同,
        那么 while 循环就需要执行 n次,每次忽略区间的右端点,时间复杂度为 O(n)。
        */
        int n = nums.size();
        int left = 0, right = n - 1;
        //这里控制条件没取等号,取等号大多是为了在while中直return mid,不取等号就跳出while返回left的值
        while(left < right) {
            int mid = left + (right - left) / 2;
            if(nums[mid] > nums[right]) {
                //中间数字大于右边数字,比如[3,4,5,1,2],则左侧是有序上升的,最小值在右侧
                left = mid + 1;
            } else if(nums[mid] < nums[right]) {
                //中间数字小于等于右边数字,比如[6,7,1,2,3,4,5],则右侧是有序上升的,最小值在左侧
                right = mid;
            } else {
                // 中间数字等于右边数字,比如[2,3,1,1,1]或者[4,1,2,3,3,3,3]
                // 则重复数字可能为最小值,也可能最小值在重复值的左侧
                // 所以将right左移一位
                right -= 1;
            }
        }
        return nums[left];
    }
};

33. 搜索旋转排序数组

整数数组 nums 按升序排列,数组中的值 互不相同 。

在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], …, nums[n-1], nums[0], nums[1], …, nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2]

给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0, right= nums.size()-1;
        //这里控制条件取等号,取等号大多是为了在while中直return mid,不取等号就跳出while返回l的值。
        while(left <= right) {
            int mid = left + (right - left)/2;

            if(nums[mid] == target) return mid;
            if(nums[left] < nums[mid]) {//左半部分是有序
                if(nums[left] <= target && target < nums[mid]) right = mid - 1;//target落在左半部分有序区域内
                else left = mid + 1;//target落在右半部分无序区域内
            } else {//右半部分是有序
                if(nums[mid] < target && target <= nums[right]) left = mid + 1;//target落在右半部分有序区域内
                else right = mid - 1;//target落在左半部分无序区域内
            }
        }
        return -1;
    }
};

面试题 10.03. 搜索旋转数组

搜索旋转数组。给定一个排序后的数组,包含n个整数,但这个数组已被旋转过很多次了,次数不详。请编写代码找出数组中的某个元素,假设数组元素原先是按升序排列的。若有多个相同元素,返回索引值最小的一个。

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0, right= nums.size()-1;
        //int res = -1;
        while(left <= right) {
            //当left符合时直接返回, 因为找的是最小的索引
            if(nums[left] == target) return left;
            int mid = left + (right - left)/2;


            //当中间值等于目标值,将右边界移到中间,因为左边可能还有相等的值
            if(target == nums[mid]) right = mid;
            else if(nums[left] == nums[right]) left += 1;//当中间数字与左边数字相等时,将左边界右移
            else if(nums[left] < nums[mid]) { //左边有序
                if(nums[left] <= target && target < nums[mid]) right = mid - 1;
                else left = mid + 1;
            } else if(nums[left] > nums[mid]) {
                if(nums[mid] < target && target <= nums[right]) left = mid + 1;
                else right = mid - 1;
            } 
        }
        return -1;
    }
};

324. 摆动排序 II

给你一个整数数组 nums,将它重新排列成 nums[0] < nums[1] > nums[2] < nums[3]… 的顺序。

你可以假设所有输入数组都可以得到满足题目要求的结果。

class Solution {
public:
    void wiggleSort(vector<int>& nums) {
        //将数组分为左右
        vector<int>res = nums;
        sort(res.begin(), res.end());
        //其实可以稍微改进一下,我们可以先放置峰值,这样就是res从右到左一次遍历即可
        int start = (nums.size()-1)/2;
        for(int i = 0; start >= 0; start--, i+=2) {
            nums[i] = res[start];
        }
        start = nums.size()-1;
        for(int i = 1; i < nums.size(); start--, i+=2) {
            nums[i] = res[start];
        }
    }
};

162. 寻找峰值(☆☆)

峰值元素是指其值大于左右相邻值的元素。

给你一个输入数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。

你可以假设 nums[-1] = nums[n] = -∞ 。

时间复杂度要求为O(logn),因此必然是二分查找
如果mid符合要求,则返回mid(运气好而已而且存在限制)
如果mid不符合要求,那么mid就要和mid+1进行比较:
中间的小于右侧的,那么右侧必然有一个“升序的趋势”,因此缩小区间可以往右侧(区间不包含中间)
中间大于右侧,左侧必然是升序的趋势,往左侧缩小区间(区间包含中间)
一直到区间为1即循环变量left==right为止

class Solution {
public:
    int findPeakElement(vector<int>& nums) {
        //二分法
        int left = 0, right = nums.size()-1;
        while(left < right) {
            int mid = left + (right - left) / 2;
            if(nums[mid] > nums[mid+1]) {
                right = mid;
            } else {
                left = mid+1;
            }
        } 
        return left;
    }
};

378. 有序矩阵中第 K 小的元素(☆☆☆)

给你一个 n x n 矩阵 matrix ,其中每行和每列元素均按升序排序,找到矩阵中第 k 小的元素。
请注意,它是 排序后 的第 k 小元素,而不是第 k 个 不同 的元素。
二分,大顶堆,归并排序

704. 二分查找

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

二分查找如何实现查找左边界和右边界呢?
左右边界索引

class Solution {
public:
    int search(vector<int>& nums, int target) {
        //定义 target 是在一个在左闭右开的区间里,也就是[left, right),那么判断的时候就要注意while(left < right)
        int left = 0, right = nums.size();
        while(left < right)
        {
            int mid = left + (right-left) / 2;//以防止left+right溢出(超出整数范围
            if(nums[mid] < target) left = mid + 1;
            else if(nums[mid] > target) right = mid;
            else return mid;
        }
        return -1;
    }
};
class Solution {
public:
    int search(vector<int>& nums, int target) {
        //定义 target 是在一个在左闭右闭的区间里,也就是[left, right],那么判断的时候就要注意while(left<= right)
        int left = 0, right = nums.size()-1;
        while(left <= right)
        {
            int mid = left + (right-left) / 2;
            if(nums[mid] < target) left = mid + 1;
            else if(nums[mid] > target) right = mid - 1;
            else return mid;
        }
        return -1;
    }
};

35. 搜索插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

你可以假设数组中无重复元素。

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int left = 0, right = nums.size();
        while(left < right)
        {
            int mid = left + (right - left) / 2;
            if(nums[mid] < target) left = mid + 1;
            else if(nums[mid] > target) right = mid;
            else return mid;
        }
        return left;
    }
};
class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int left = 0, right = nums.size()-1;
        while(left <= right)
        {
            int mid = left + (right - left) / 2;
            if(nums[mid] < target) left = mid + 1;
            else if(nums[mid] > target) right = mid - 1;
            else return mid;
        }
        return left;
    }
};

34. 在排序数组中查找元素的第一个和最后一个位置(☆☆)

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]。

进阶:
你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?

思路:利用简单的二分可以实现先找到索引,然后左右遍历寻找左右边界

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int left = 0, right = nums.size()-1;
        while(left <= right)
        {
            int mid = left + (right - left) / 2;
            if(nums[mid] < target) left = mid + 1;
            else if(nums[mid] > target) right = mid - 1;
            else return mid;
        }
        return left;
    }
};

直接利用二分寻找左右边界

        /*
        考虑target 开始和结束位置,其实我们要找的就是数组中「第一个等于 target 的位置」(记为 leftIdx)和「第一个大于target 的位置减一」(记为rightIdx)。

    二分查找中,寻找 leftIdx 即为在数组中寻找第一个大于等于 target 的下标,寻找 rightIdx 即为在数组中寻找第一个大于 target 的下标,然后将下标减一。两者的判断条件不同,为了代码的复用,我们定义 binarySearch(nums, target,    lower) 表示在 nums 数组中二分查找 target 的位置,如果 lower 为 true,则查找第一个大于等于target 的下标,否则查找第一个大于target 的下标。

        最后,因为 target 可能不存在数组中,因此我们需要重新校验我们得到的两个下标 leftIdx 和 rightIdx,看是否符合条件,如果符合条件就返回 [leftIdx,rightIdx],不符合就返回 [-1,-1]

        */
class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int left = binarySearch(nums, target, true);//求左边界
        int right = binarySearch(nums, target, false);//求右边界
        if(left <= right && right < nums.size() && nums[left] == target && nums[right] == target) {
            return vector<int>{left, right};
        }

        return vector<int>{-1, -1};
    }

    int binarySearch(vector<int>& nums, int target, bool lower) {
        int left = 0, right = nums.size()-1;
        int res = -1;

        while(left <= right) {
            int mid = left + (right - left)/2;
            if(nums[mid] > target) {
                right = mid - 1;
            } else if(nums[mid] < target){
                left = mid + 1;
            } else if(nums[mid] == target) {
                res = mid;
                if(lower) right = mid - 1;
                else left = mid + 1;
            }
        }
        return res;
    }
};
        

69. x 的平方根

实现 int sqrt(int x) 函数。

计算并返回 x 的平方根,其中 x 是非负整数。

由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。

class Solution {
public:
    int mySqrt(int x) {
        if(x == 0 || x == 1) return x;
        int left = 0, right = x;
        while(left <= right) {
            int mid = left + (right - left) / 2;
            if(x/mid < mid) {right = mid - 1;}
            else if(x / mid > mid) left = mid + 1;
            else return mid;
        }
        return right;
    }
};

367. 有效的完全平方数(☆)

给定一个 正整数 num ,编写一个函数,如果 num 是一个完全平方数,则返回 true ,否则返回 false 。

进阶:不要 使用任何内置的库函数,如 sqrt 。

class Solution {
public:
    bool isPerfectSquare(int num) {
       long left = 0, right = num / 2;
        if (num < 2) {
            return true;
        }
        while (left <= right) {
            long mid = left + (right - left) / 2;
            if (mid * mid == num) {
                return true;
            } else if (mid * mid > num) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return false;
    }
};

双指针

面试题 16.06. 最小差(☆☆☆)

给定两个整数数组a和b,计算具有最小差绝对值的一对数值(每个数组中取一个值),并返回该对数值的差

class Solution {
public:
    int smallestDifference(vector<int>& a, vector<int>& b) {
        /*
        假设你把两个数组排序,然后遍历它们。如果第一个数组中的指针指向3,第二个数组中的指针指向9,那么移动第二个指针会对这一对数字的差产生什么影响?
        */
        sort(a.begin(), a.end());
        sort(b.begin(), b.end());
        int i = 0; int j = 0;
        int res = INT_MAX;
        while(i < a.size() && j < b.size()) {
            //使用 long,防止 -2147483648 转正数后还是 -2147483648
            long diff = a[i] - b[j];
            if(abs(diff) < res) res = abs(diff);
            if(diff < 0) i++;
            else j++;
        }
        return res;
    }
};

915. 分割数组(☆☆)

给定一个数组 nums ,将其划分为两个连续子数组 left 和 right, 使得:

  • left 中的每个元素都小于或等于 right 中的每个元素。
  • left 和 right 都是非空的。
  • left 的长度要尽可能小。
    在完成这样的分组后返回 left 的 长度 。

用例可以保证存在这样的划分方法。

示例 1:

输入:nums = [5,0,3,8,6]
输出:3
解释:left = [5,0,3],right = [8,6]

示例 2:

输入:nums = [1,1,1,0,6,12]
输出:4
解释:left = [1,1,1,0],right = [6,12]

提示:

2 <= nums.length <= 105
0 <= nums[i] <= 106
可以保证至少有一种方法能够按题目所描述的那样对 nums 进行划分。

解题思路:
双指针:
如果存在一个index使得[0, index]的最大值小于[index+1, n-1],就符合要求,也就是说左边的最大值小于右边的最小值,因此必须设置两个指针,一个指向左边最大值leftMaxVal,一个指向数组最大值
具体代码实现时,遍历数组,首先比较出数组最大值,然后时leftMaxVal和nums[i],如果nums[i]>=leftMaxval,那么就continue,否则就将最大值赋给leftMaxVal

class Solution {
public:
    int partitionDisjoint(vector<int>& nums) {
        //双指针
        int maxVal = nums[0];
        int leftMaxVal = nums[0];
        int pos = 0;
        for(int i = 1; i < nums.size(); i++) {
            maxVal = max(nums[i], maxVal);
            if(nums[i] >= leftMaxVal)
                continue;
            leftMaxVal = maxVal;
            pos = i;
        }
        return pos+1;
    }
};

904. 水果成篮(☆☆)

你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示,其中 fruits[i] 是第 i 棵树上的水果 种类 。

你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果:

你只有 两个 篮子,并且每个篮子只能装 单一类型 的水果。每个篮子能够装的水果总量没有限制。
你可以选择任意一棵树开始采摘,你必须从 每棵 树(包括开始采摘的树)上 恰好摘一个水果 。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。
一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。
给你一个整数数组 fruits ,返回你可以收集的水果的 最大 数目。

示例 1:

输入:fruits = [1,2,1]
输出:3
解释:可以采摘全部 3 棵树。

示例 2:

输入:fruits = [0,1,2,2]
输出:3
解释:可以采摘 [1,2,2] 这三棵树。
如果从第一棵树开始采摘,则只能采摘 [0,1] 这两棵树。

示例 3:

输入:fruits = [1,2,3,2,2]
输出:4
解释:可以采摘 [2,3,2,2] 这四棵树。
如果从第一棵树开始采摘,则只能采摘 [1,2] 这两棵树。

示例 4:

输入:fruits = [3,3,3,1,2,1,1,2,3,3,4]
输出:5
解释:可以采摘 [1,2,1,1,2] 这五棵树。

提示:

1 <= fruits.length <= 10 ^5
0 <= fruits[i] < fruits.length

解题思路:
简单模拟:可以简单理解未求只含两种元素的最长连续子序列
利用双指针left, right, 初始两个边界ln, rn,right向右滑动,如果right边界值是ln或者rn,那么继续滑动;

2、如果right值不是ln、rn中的一个,那么left移动到right前一位,更新ln和rn,并向左移动至相同元素最左侧
class Solution {
public:
    int totalFruit(vector<int>& fruits) {
        int left = 0, right=0, count = 0;
        int ln = fruits[left], rn = fruits[right];
        while(right < fruits.size()) {
            if(fruits[right] == ln || fruits[right] == rn) {
                count = max(count, right-left+1);
                right++;
            } else {
                left = right-1;
                ln = fruits[left];
                rn = fruits[right];
                while(left >= 1 && fruits[left-1] == ln) left--;
            }
        }
        return count;
    }
};

238. 除自身以外数组的乘积(☆☆☆)

给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。

class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) {
        
        int n=nums.size();
        int left=1,right=1;     //left:从左边累乘,right:从右边累乘
        vector<int> res(n,1);
        
        for(int i=0;i<n;++i)    //最终每个元素其左右乘积进行相乘得出结果
        {
            res[i]*=left;       //乘以其左边的乘积
            left*=nums[i];
            
            res[n-1-i]*=right;  //乘以其右边的乘积
            right*=nums[n-1-i];
        }
        
        return res;
        
    }
};

287. 寻找重复数(☆☆☆)

给定一个包含 n + 1 个整数的数组 nums ,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。

假设 nums 只有 一个重复的整数 ,找出 这个重复的数 。

你设计的解决方案必须不修改数组 nums 且只用常量级 O(1) 的额外空间。

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
         /**
        快慢指针思想, fast 和 slow 是指针, nums[slow] 表示取指针对应的元素
        注意 nums 数组中的数字都是在 1 到 n 之间的(在数组中进行游走不会越界),
        因为有重复数字的出现, 所以这个游走必然是成环的, 环的入口就是重复的元素, 
        即按照寻找链表环入口的思路来做
        **/
        int fast = 0, slow = 0;
        while(1) {
            fast = nums[nums[fast]];
            slow = nums[slow];
            if(slow == fast) {
                fast = 0;
                while(nums[slow] != nums[fast]) {
                    fast = nums[fast];
                    slow = nums[slow];
                }
                return nums[slow];
            }
        }
        return -1;
    }
};

26. 删除有序数组中的重复项(☆)

给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。

不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        if(nums.size() < 2) return nums.size();
        //双指针
        int slow = 0;
        for(int i = 1; i < nums.size(); i++)
        {
            if(nums[slow] != nums[i]) {
                nums[++slow] = nums[i];
            }
        }
        return ++slow;
    }
};

80. 删除有序数组中的重复项 II(☆)

给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 最多出现两次 ,返回删除后数组的新长度。

不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

允许重复两个就是直接快慢指针相隔两个索引

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        //双指针
        if(nums.size() < 3) return nums.size();
        int index = 0;
        for(int n : nums)
        {
            if(index < 2 || n > nums[index-2])//最多两个,如果是3个呢
            {
                nums[index++] = n;
            }
        }
        return index;
    }
};

27. 移除元素(☆)

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        //双指针
        if(nums.size() == 0) return 0;
        int slow = 0;
        for(int fast = 0; fast < nums.size(); fast++)
        {
            if(nums[fast] != val)
            {
                nums[slow] = nums[fast];
                slow++;
            }
        }
        return slow;
    }
};

977. 有序数组的平方

给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        //双指针
        int left = 0, right = nums.size()-1;
        vector<int>res(nums.size());
        int index = nums.size()-1;
        while(left <= right)
        {
            if(nums[left]*nums[left] < nums[right]*nums[right])
            {
                res[index] = nums[right] * nums[right];
                right--;
                index--;
            }
            else
            {
                res[index] = nums[left] * nums[left];
                left++;
                index--;
            }
        }
        return res;
    }
};

滑动窗口

滑动窗口:主要思想是右侧添加字符和左侧删去都需要进行更新数据
/* 滑动窗口算法框架 */
void slidingWindow(string s, string t) {
    unordered_map<char, int> need, window;
    for (char c : t) need[c]++;

    int left = 0, right = 0;
    int valid = 0; 
    while (right < s.size()) {
        // c 是将移入窗口的字符
        char c = s[right];
        // 右移窗口
        right++;
        // 进行窗口内数据的一系列更新
        ...

        /*** debug 输出的位置 ***/
        printf("window: [%d, %d)\n", left, right);
        /********************/

        // 判断左侧窗口是否要收缩
        while (window needs shrink) {
            // d 是将移出窗口的字符
            char d = s[left];
            // 左移窗口
            left++;
            // 进行窗口内数据的一系列更新
            ...
        }
    }
}

209. 长度最小的子数组

给定一个含有 n 个正整数的数组和一个正整数 target 。

找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        //滑动窗口
        int result = INT32_MAX;
        int sum = 0; // 滑动窗口数值之和
        int i = 0; // 滑动窗口起始位置
        int subLength = 0; // 滑动窗口的长度
        for (int j = 0; j < nums.size(); j++) {
            sum += nums[j];
            // 注意这里使用while,每次更新 i(起始位置),并不断比较子序列是否符合条件
            while (sum >= target) {
                subLength = (j - i + 1); // 取子序列的长度
                result = result < subLength ? result : subLength;
                sum -= nums[i++]; // 这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置)
            }
        }
        // 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
        return result == INT32_MAX ? 0 : result;
    }
};

76. 最小覆盖子串(☆☆)

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。

注意:如果 s 中存在这样的子串,我们保证它是唯一的答案。

class Solution {
public:
    string minWindow(string s, string t) {
        //滑动窗口
        //string str = s;
        unordered_map<char, int>need, window;
        for(auto c : t) need[c]++;

        int left = 0, right = 0;
        int vaild = 0;
        int start = 0;
        int len = INT_MAX;
        while(right < s.size())
        {
            // c 是将移入窗口的字符
            char c = s[right];
             // 右移窗口
            right++;

            //窗口更新
            if(need.find(c) != need.end())
            {
                window[c]++;
                if(window[c] == need[c])
                {
                    vaild++;
                }
            }

            while(vaild == need.size())
            {
                //首先进行判断操作,数据更新永远是最后的
                if(right - left < len)
                {
                    start = left;
                    len = right-left;
                }
                char d =s[left];
                left++;

                if(need.find(d) != need.end())
                {
                    window[d]--;
                    if(window[d] < need[d])
                    {
                        vaild--;
                    }
                }
            }

        }
        return len == INT_MAX? "" : s.substr(start, len);
    }
};

239. 滑动窗口最大值(☆☆☆)

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        /*
        思路: 遍历数组 L R 为滑窗左右边界 只增不减
        双向队列保存当前窗口中最大的值的数组下标 双向队列中的数从大到小排序,
        新进来的数如果大于等于队列中的数 则将这些数弹出 再添加
        当R-L+1=k 时 滑窗大小确定 每次R前进一步L也前进一步 保证此时滑窗中最大值的
        数组下标在[L,R]中,并将当前最大值记录
        举例: nums[1,3,-1,-3,5,3,6,7] k=3
            1:L=0,R=0,队列【0】 R-L+1 < k
                    队列代表值【1】
            2: L=0,R=1, 队列【1】 R-L+1 < k
                    队列代表值【3】
            解释:当前数为3 队列中的数为【1】 要保证队列中的数从大到小 弹出1 加入3
                但队列中保存的是值对应的数组下标 所以队列为【1】 窗口长度为2 不添加记录
            3: L=0,R=2, 队列【1,2】 R-L+1 = k ,result={3}
                    队列代表值【3,-1】
            解释:当前数为-1 队列中的数为【3】 比队列尾值小 直接加入 队列为【3,-1】
                窗口长度为3 添加记录记录为队首元素对应的值 result[0]=3
            4: L=1,R=3, 队列【1,2,3】 R-L+1 = k ,result={3,3}
                    队列代表值【3,-1,-3】
            解释:当前数为-3 队列中的数为【3,-1】 比队列尾值小 直接加入 队列为【3,-1,-3】
                窗口长度为4 要保证窗口大小为3 L+1=1 此时队首元素下标为1 没有失效
                添加记录记录为队首元素对应的值 result[1]=3
            5: L=2,R=4, 队列【4】 R-L+1 = k ,result={3,3,5}
                    队列代表值【5】
            解释:当前数为5 队列中的数为【3,-1,-3】 保证从大到小 依次弹出添加 队列为【5】
                窗口长度为4 要保证窗口大小为3 L+1=2 此时队首元素下标为4 没有失效
                添加记录记录为队首元素对应的值 result[2]=5
            依次类推 如果队首元素小于L说明此时值失效 需要弹出
        *时间复杂度:o()
        *空间复杂度:o()
        */

        vector<int> res;
        deque<int> que;
        for(int i = 0; i < nums.size(); i++) {
            if(!que.empty() && que.front() == i-k) que.pop_front();
            while(!que.empty() && nums[i] > nums[que.back()]) que.pop_back();
            que.push_back(i);

            if(i >= k-1) res.push_back(nums[que.front()]);
        }
        return res;
    }

};

1004. 最大连续1的个数 III(☆)

给定一个由若干 0 和 1 组成的数组 A,我们最多可以将 K 个值从 0 变成 1 。

返回仅包含 1 的最长(连续)子数组的长度。

class Solution {
public:
    int longestOnes(vector<int>& nums, int k) {
        //滑动窗口
        if(k >= nums.size()) return nums.size();
        int countZero = 0;
       
        int left = 0, right = 0;
        int len = INT_MIN;

        while(right < nums.size()) {
           if(nums[right] == 0) countZero++;
           right++;
           

            //窗口收缩
            while(left < right && countZero > k) {
                if(nums[left] == 0) countZero--;
                left++;
            }
            len = max(len, right-left);//计算正确的长度
        }
        return len;
    }
};

713. 乘积小于K的子数组

给定一个正整数数组 nums和整数 k 。

请找出该数组内乘积小于 k 的连续的子数组的个数。

class Solution {
public:
    int numSubarrayProductLessThanK(vector<int>& nums, int k) {
        if (k <= 1) return 0;
        int prod = 1, ans = 0, left = 0;
        for (int right = 0; right < nums.size(); right++) {
            prod *= nums[right];
            while (prod >= k) prod /= nums[left++];
             //一旦左指针向右移动到某个位置时子数组乘积小于k,就不需要在继续移动了
            //因为继续向右移动形成的子数组之积必定也小于k
            //此时两个指针之间有多少个数字,就找到了多少个数字乘积小于k的子数组

            ans += right - left + 1;
        }
        return ans;
    }
};

归并排序

395. 至少有 K 个重复字符的最长子串

给你一个字符串 s 和一个整数 k ,请你找出 s 中的最长子串, 要求该子串中的每一字符出现次数都不少于 k 。返回这一子串的长度。

class Solution {
public:
    int longestSubstring(string s, int k) {
        //分治算法
        if(k <= 1) return s.size();

        vector<int> hash(128, 0);
        for(char c:s) hash[c]++;

        int i = 0;
        while(i < s.size() && hash[s[i]] >= k) ++i;
        if(i == s.size()) return s.size();

        int left = longestSubstring(s.substr(0, i), k);
        while(i < s.size() && hash[s[i]] < k) ++i;

        int right = longestSubstring(s.substr(i), k);
        return max(left, right);
    }
};

剑指 Offer 51. 数组中的逆序对

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。

class Solution {
public:
    
    int reversePairs(vector<int>& nums) {
        vector<int> vec(nums.size());
        return mergeSort(0, nums.size() - 1, nums, vec);
    }

    int mergeSort(int left, int right, vector<int>& nums, vector<int>& vec) {
        //终止条件
        if(left >= right) return 0;
        //递归划分
        int mid = (left+right)/2;
        int res = mergeSort(left, mid, nums, vec) + mergeSort(mid+1, right, nums, vec);

        //合并阶段
        int i = left, j = mid + 1;
        for(int k = left; k <= right; k++){
            vec[k] = nums[k];
        }

        for(int k = left; k <= right; k++){
            if(i == mid+1) {
                nums[k] = vec[j++];
            } else if(j == right+1 || vec[i] <= vec[j]) {
                nums[k] = vec[i++];
            } else if(vec[i] > vec[j]){
                nums[k] = vec[j++];
                res += mid-i+1;//统计逆序对
            }
        }
        return res;
    }
};

数组技巧型题

710. 黑名单中的随机数

给定一个包含 [0,n) 中不重复整数的黑名单 blacklist ,写一个函数从 [0, n) 中返回一个不在 blacklist 中的随机整数。

对它进行优化使其尽量少调用系统方法 Math.random() 。

提示:

1 <= n <= 1000000000
0 <= blacklist.length < min(100000, N)
[0, n) 不包含 n ,详细参见 interval notation 。

class Solution {
public:
    int sz;
    unordered_map<int,int>m;
    /*
    白名单中数的个数为 N - len(B),那么可以直接在 [0, N - len(B)) 中随机生成整数。
    我们把所有小于 N - len(B) 且在黑名单中数一一映射到大于等于 N - len(B) 且出现在白名单中的数。
    这样一来,如果随机生成的整数出现在黑名单中,我们就  返回它唯一对应的那个出现在白名单中的数即可。

    例如当 N = 6,B = [0, 2, 3] 时,我们在 [0, 3) 中随机生成整数,并将 2 映射到 4,3 映射到 5,
    这样随机生成的整数就是 [0, 1, 4, 5] 中的一个。算法我们将黑名单分成两部分,第一部分 X 的数都小于 N - len(B),
    需要进行映射;第二部分 Y 的数都大于等于 N - len(B),这些数不需要进行映射,因为并不会随机到它们。

    我们先用 Y 构造出 W,表示大于等于 N - len(B) 且在白名单中的数,X 和 W 的长度一定是相等的。
    随后遍历 X 和 W,构造一个映射表(HashMap)M,将 X 和 W 中的数构造一一映射。

    在 [0, N - len(B)) 中随机生成整数 a 时,如果 a 出现在 M 中,则将它的映射返回,否则直接返回 a。

    */
    Solution(int N, vector<int>& blacklist) {
        sz = N - blacklist.size();
        //先将所有黑名单加入map
        for(int b : blacklist) m[b] = 250;
        //将白名单进行映射
        int last = N - 1;
        for(int b : blacklist) {
            if(b >= sz) continue;//b如果大于sz,那么就不需要进行映射,因为我们是要在[0,sz)之间进行映射
            while(m.count(last)) last--;//找到一个last不在黑名单中
            m[b] = last;
            last--;

        }
    }
    
    int pick() {
        int index = rand() % sz;
        if(m.count(index)) return m[index];
        return index;
    }
};



769. 最多能完成排序的块

给定一个长度为 n 的整数数组 arr ,它表示在 [0, n - 1] 范围内的整数的排列。

我们将 arr 分割成若干 块 (即分区),并对每个块单独排序。将它们连接起来后,使得连接的结果和按升序排序后的原数组相同。

返回数组能分成的最多块数量。

示例 1:
输入: arr = [4,3,2,1,0]
输出: 1
解释:
将数组分成2块或者更多块,都无法得到所需的结果。
例如,分成 [4, 3], [2, 1, 0] 的结果是 [3, 4, 0, 1, 2],这不是有序的数组。

示例 2:
输入: arr = [1,0,2,3,4]
输出: 4
解释:
我们可以把它分成两块,例如 [1, 0], [2, 3, 4]。
然而,分成 [1, 0], [2], [3], [4] 可以得到最多的块数。

提示:
n == arr.length
1 <= n <= 10
0 <= arr[i] < n
arr 中每个元素都 不同

解题思路:
贪心算法:
因为是[0, n-1]的数列,
当遍历第i个位置时,如果可以划分成块,那么第i个位置的最大值一定等于i
否则,一定有比i小的数划分到后面的块,就一定不满足升序,也就不满足划块

class Solution {
public:
    int maxChunksToSorted(vector<int>& arr) {
        int val = 0, res =0;
        for(int i = 0; i < arr.size(); i++) {
            val = max(arr[i], val);
            if(val == i) res++;
        }
        return res;
    }
};

437. 路径总和 III

给定一个二叉树,它的每个结点都存放着一个整数值。

找出路径和等于给定数值的路径总数。

路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。

二叉树不超过1000个节点,且节点数值范围是 [-1000000,1000000] 的整数。

class Solution {
public:
    
    int pathSum(TreeNode* root, int targetSum) {
        /*首先前序遍历对每一个节点进行计算,然后以每一个节点为根节点进行计算*/
        if(root == nullptr) return 0;
        int count = 0;
        count += dfs(root, targetSum, 0);
        count += pathSum(root->left, targetSum);
        count += pathSum(root->right, targetSum);
        
        return count;
    }

    int dfs(TreeNode* root, int targetSum, int sum) {
        //继续前序遍历
        if(root == nullptr) return 0;
        int tmp = 0;
        if(sum + root->val == targetSum) tmp++;
        
        return tmp + dfs(root->left, targetSum, sum + root->val) + dfs(root->right, targetSum, sum + root->val);
           
    }    
};



371. 两整数之和

不使用运算符 + 和 - ​​​​​​​,计算两整数 ​​​​​​​a 、b ​​​​​​​之和。

class Solution {
public:
    int getSum(int a, int b) {
        int sum, carry; 
        sum = a ^ b;  //异或这里可看做是相加但是不显现进位,比如5 ^ 3
                    /*0 1 0 1
                    0 0 1 1
                    ------------
                    0 1 1 0      
                上面的如果看成传统的加法,不就是1+1=2,进1得0,但是这里没有显示进位出来,仅是相加,0+1或者是1+0都不用进位*/
        
        carry = ((unsigned int)a & b) << 1;
        
                    //相与为了让进位显现出来,比如5 & 3
                    /* 0 1 0 1
                    0 0 1 1
                    ------------
                    0 0 0 1
                上面的最低位1和1相与得1,而在二进制加法中,这里1+1也应该是要进位的,所以刚好吻合,但是这个进位1应该要再往前一位,所以左移一位*/
        
        if(carry != 0)  //经过上面这两步,如果进位不等于0,那么就是说还要把进位给加上去,所以用了尾递归,一直递归到进位是0。
        {
            return getSum(sum, carry);
        }
        return sum;
        
    }
};

169. 多数元素

给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        摩尔投票法,先假设第一个数过半数并设cnt=1;遍历后面的数如果相同则cnt+1,不同则减一,
        //当cnt为0时则更换新的数字为候选数(成立前提:有出现次数大于n/2的数存在)
        int res = 0, cnt = 0;
        for(int i = 0; i < nums.size(); i++) {
            if(cnt == 0) {
                res = nums[i];
                cnt++;
            }
            else {
                res == nums[i]? cnt++ : cnt--;
            }
        }
        return res;
    }
};

59. 螺旋矩阵 II

给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。

主要是考察代码能力

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        int rowTop = 0, colLeft = 0;
        int rowDown = n-1, colRight = n-1;

        vector<vector<int>> res(n, vector<int>(n));

        int count = 1;
        while(count <= n*n)
        {
            //上行
            for(int j = colLeft; j <= colRight; j++) 
            {
                res[rowTop][j] = count;
                count++;
            }
            //右列
            for(int i = rowTop+1; i <= rowDown; i++)
            {
                res[i][colRight] = count;
                count++;
            }

            //下行
            for(int j = colRight-1; j >= colLeft; j--)
            {
                res[rowDown][j] = count;
                count++;
            }
            //左列
            for(int i = rowDown-1; i > rowTop; i--)
            {
                res[i][colLeft] = count;
                count++;
            }
            rowTop++;
            rowDown--;
            colLeft++;
            colRight--;
        }
        return res;
    }
};

54. 螺旋矩阵

给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。

class Solution {
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) {
        int m = matrix.size(), n = matrix[0].size();
        int rowTop = 0, colLeft = 0;
        int rowDown = m-1, colRight = n-1;

        vector<int>res;

        int count = 1;
        while(count <= m*n)
        {
            //上行
            for(int j = colLeft; j <= colRight; j++) 
            {
                res.push_back(matrix[rowTop][j]);
                count++;
            }
            if(count > m * n) break;

            //右列
            for(int i = rowTop+1; i <= rowDown; i++)
            {
                res.push_back(matrix[i][colRight]);
                count++;
            }
            if(count > m * n) break;

            //下行
            for(int j = colRight-1; j >= colLeft; j--)
            {
                res.push_back(matrix[rowDown][j]);
                count++;
            }
            if(count > m * n) break;
            
            //左列
            for(int i = rowDown-1; i > rowTop; i--)
            {
                res.push_back(matrix[i][colLeft]);
                count++;
            }
            rowTop++;
            rowDown--;
            colLeft++;
            colRight--;
        }
        return res;
    }
};

347. 前 K 个高频元素(☆☆)

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

利用小顶堆实现k个数组的排序
要统计元素出现频率
对频率排序
找出前K个高频元素
注意:求前 k 大,用小根堆,求前 k 小,用大根堆

class Solution {
public:
    class mycomparison {
    public:
        bool operator() (const pair<int, int>& lhs, const pair<int, int>& rhs) {
            return lhs.second > rhs.second;
        }
    };
    
    vector<int> topKFrequent(vector<int>& nums, int k) {
        // 时间复杂度:O(nlogk)
        // 空间复杂度:O(n)
        //统计出现饿频率
        unordered_map<int, int> map;
        for(int i = 0; i < nums.size(); i++) {
            map[nums[i]]++;
        }

        // 对频率排序
        // 定义一个小顶堆,大小为k
        priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que;

        //用固定大小为k的小顶堆,扫描所有频率的数值
        for(unordered_map<int, int>::iterator it = map.begin(); it != map.end(); it++) {
            pri_que.push(*it);
            if (pri_que.size() > k) { // 如果堆的大小大于了K,则队列弹出,保证堆的大小一直为k
                pri_que.pop();
            }
        }

        // 找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒叙来输出到数组
        vector<int> result(k);
        for(int i = k-1; i >= 0; i--) {
            result[i] = pri_que.top().first;
            pri_que.pop();
        }
        return result;
    }
};

42. 接雨水(☆☆)

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

对于vector中每个点,水有多高取决于这个点左侧和右侧墙壁的最大高度。
第一个for循环找每个点的左侧最大高度,第二个for循环找每个点右侧的最大高度,循环中跳过最左侧(i=0)和最右侧点(i=vector.size()-1)的原因是这两个点由于没有左侧墙壁或右侧墙壁所以最大墙壁高度肯定是0,故在初始化vector的时候已经将其默认设置成0了。
在得到所有点的左右墙壁最大高度后,木桶原理取左右墙壁较低的那个高度减去当前位置墙壁作为地面的高度就得到了这个位置上水的高度。然后将所有点的水高度相加即为解

class Solution {
public:
    int trap(vector<int>& height) {
        int n = height.size();
        // left[i]表示i左边的最大值,right[i]表示i右边的最大值
		vector<int> left(n), right(n);
        for(int i = 1; i < n; i++) {
            left[i] = max(left[i-1], height[i-1]);
        }
        for(int i = 0; i < n; i++) cout<<left[i]<<" ";
        cout<<endl;
        for(int i = n-2; i >= 0; i--) {
            right[i] = max(right[i+1], height[i+1]);
        }
        for(int i = 0; i < n; i++) cout<<right[i]<<" ";
        int water = 0;
        for(int i = 0; i < n; i++) {
            int level = min(left[i], right[i]);
            water += max(0, level - height[i]);
        }
        return water;
    }
};

779. 第K个语法符号

我们构建了一个包含 n 行( 索引从 1 开始 )的表。首先在第一行我们写上一个 0。接下来的每一行,将前一行中的0替换为01,1替换为10。

例如,对于 n = 3 ,第 1 行是 0 ,第 2 行是 01 ,第3行是 0110 。
给定行数 n 和序数 k,返回第 n 行中第 k 个字符。( k 从索引 1 开始)

链表

结点的定义:
// 单链表
struct ListNode {
    int val;  // 节点上存储的元素
    ListNode *next;  // 指向下一个节点的指针
    ListNode(int x) : val(x), next(NULL) {}  // 节点的构造函数
};

203. 移除链表元素

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。

主要思想就是通常我们会设置一个虚拟结点来进行删除操作

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        ListNode* dummy = new ListNode();
        dummy->next = head;
        ListNode* cur = dummy;
        while(cur->next){
            if(cur->next->val == val) {
                ListNode* tmp = cur->next;
                cur->next = cur->next->next;
                delete tmp;
            } else {
                cur = cur->next;
            }
        }
        head = dummy->next;
        delete dummy;
        return head;
    }
};

707. 设计链表(☆☆)

设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val 和 next。val 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。

在链表类中实现这些功能:

get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。

class MyLinkedList {
public:
     // 定义链表节点结构体
    struct LinkedNode {
        int val;
        LinkedNode* next;
        LinkedNode(int val):val(val), next(nullptr){}
    };
    /** Initialize your data structure here. */
    MyLinkedList() {
        _dummyHead = new LinkedNode(0); // 这里定义的头结点 是一个虚拟头结点,而不是真正的链表头结点
        _size = 0;
    }
    
    /** Get the value of the index-th node in the linked list. If the index is invalid, return -1. */
    int get(int index) {
        if (index > (_size - 1) || index < 0) {
            return -1;
        }
        LinkedNode* cur = _dummyHead->next;
        while(index--){ // 如果--index 就会陷入死循环
            cur = cur->next;
        }
        return cur->val;
    }
    
    /** Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. */
    void addAtHead(int val) {
         LinkedNode* newNode = new LinkedNode(val);
        newNode->next = _dummyHead->next;
        _dummyHead->next = newNode;
        _size++;
    }
    
    /** Append a node of value val to the last element of the linked list. */
    void addAtTail(int val) {
        LinkedNode* newNode = new LinkedNode(val);
        LinkedNode* cur = _dummyHead;
        while(cur->next != nullptr){
            cur = cur->next;
        }
        cur->next = newNode;
        _size++;
    }
    
    /** Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. */
    void addAtIndex(int index, int val) {
         if (index > _size) {
            return;
        }
        LinkedNode* newNode = new LinkedNode(val);
        LinkedNode* cur = _dummyHead;
        while(index--) {
            cur = cur->next;
        }
        newNode->next = cur->next;
        cur->next = newNode;
        _size++;
    }
    
    /** Delete the index-th node in the linked list, if the index is valid. */
    void deleteAtIndex(int index) {
        if (index >= _size || index < 0) {
            return;
        }
        LinkedNode* cur = _dummyHead;
        while(index--) {
            cur = cur ->next;
        }
        LinkedNode* tmp = cur->next;
        cur->next = cur->next->next;
        delete tmp;
        _size--;
    }
private:
    int _size;
    LinkedNode* _dummyHead;
};

206. 反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
递归法:

class Solution {
public:
    ListNode* reverse(ListNode* pre, ListNode* cur){
        if(cur == nullptr) return pre;
        ListNode* tmp = cur->next;
        cur->next = pre;
        return reverse(cur, tmp);
    }

    ListNode* reverseList(ListNode* head) {
        return reverse(nullptr, head);

    }
};

迭代法:

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* pre = nullptr;
        ListNode* cur = head;
        ListNode* after;
        while(cur)
        {
            after = cur->next;
            cur->next = pre;
            //更新节点
            pre = cur;
            cur = after;          
        }
        return pre;

    }
};

24. 两两交换链表中的节点(☆)

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。

你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

直接遍历即可

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* dummmy = new ListNode();
        dummmy->next = head;
        ListNode* cur = dummmy;
        while(cur->next != nullptr && cur->next->next != nullptr)
        {
            ListNode* tmp = cur->next;
            ListNode* tmp1 = cur->next->next->next;

            cur->next = cur->next->next;//步骤1
            cur->next->next = tmp;
            cur->next->next->next = tmp1;   // 步骤三

            cur = cur->next->next; // cur移动两位,准备下一轮交换
   
        }
        return dummmy->next;
    }
};

递归:

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        if(head == nullptr || head->next == nullptr) return head;

        ListNode* next = head->next;
        head->next = swapPairs(next->next);
        next->next = head;
        return next;
    }
};

19. 删除链表的倒数第 N 个结点

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

进阶:你能尝试使用一趟扫描实现吗

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
         ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;
        ListNode* slow = dummyHead;
        ListNode* fast = dummyHead;
        while(n-- && fast != NULL) {
            fast = fast->next;
        }
        fast = fast->next; // fast再提前走一步,因为需要让slow指向删除节点的上一个节点
        while (fast != NULL) {
            fast = fast->next;
            slow = slow->next;
        }
        slow->next = slow->next->next;
        return dummyHead->next;
    }
};

160. 相交链表(☆☆)

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
在这里插入图片描述

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
    // 定义两个指针, 第一轮让两个到达末尾的节点指向另一个链表的头部, 最后如果相遇则为交点(在第一轮移动中恰好抹除了长度差)
    // 两个指针等于移动了相同的距离, 有交点就返回, 无交点就是各走了两条指针的长度

    // 在这里第一轮体现在pA和pB第一次到达尾部会移向另一链表的表头, 而第二轮体现在如果pA或pB相交就返回交点, 不相交最后就是null==null

        ListNode* pA = headA;
        ListNode* pB = headB;
        while(pA != pB)
        {
            pA = (pA == NULL)? headB:pA->next;
            pB = (pB == NULL)? headA:pB->next;
        }

        return pA;
    }
};

142. 环形链表 II

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
       ListNode* fast = head;
        ListNode* slow = head;
        while(fast != NULL && fast->next != NULL) {
            slow = slow->next;
            fast = fast->next->next;
            // 快慢指针相遇,此时从head 和 相遇点,同时查找直至相遇
            if (slow == fast) {
                ListNode* index1 = fast;
                ListNode* index2 = head;
                while (index1 != index2) {
                    index1 = index1->next;
                    index2 = index2->next;
                }
                return index2; // 返回环的入口
            }
        }
        return NULL;
    }
};

25. K 个一组翻转链表(☆☆)

给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。

k 是一个正整数,它的值小于或等于链表的长度。

如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

化整为零,将整个链表分为k个链表进行翻转,然后拼接在一起

class Solution {
public:
    ListNode* reverseKGroup(ListNode* head, int k) {
     if(head == nullptr)    return head;
     ListNode *a,*b;
     a = head; b = head;
     // 区间 [a, b) 包含 k 个待反转元素
     for(int i = 0;i<k;i++) {
         //不足k个,不需要翻转
         if(b == nullptr) return head;
         b = b->next;
     }
     ListNode *newHead = reverse(a,b);
     //翻转之后将其连接起来
     a->next = reverseKGroup(b,k);
     return newHead;  
    }
    

    /** 反转区间 [a, b) 的元素,注意是左闭右开 */
    ListNode* reverse(ListNode *a, ListNode *b) {
        ListNode *pre, *cur, *nxt;
        pre = nullptr; cur = a; nxt = a;
        while(cur != b) {
            nxt = cur->next;
            cur->next = pre;
            pre = cur;
            cur = nxt;
        }
        // 返回反转后的头结点
        return pre;
    }
};

2. 两数相加(☆)

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
在这里插入图片描述

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode* dummy = new ListNode(0);
        ListNode* cur = dummy;
        int t = 0;
        while(l1 || l2 || t != 0) {
            if(l1 != nullptr) {
                t += l1->val;
                l1 = l1->next;
            }
            if(l2 != nullptr) {
                t += l2->val;
                l2 = l2->next;
            }
            cur->next = new ListNode(t % 10);
            cur = cur->next;
            t /= 10;
        }
        return dummy->next;
    }
};

445. 两数相加 II(☆☆)

给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。

你可以假设除了数字 0 之外,这两个数字都不会以零开头。

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        //利用两个栈
        stack<int> st1, st2;
        while(l1 != nullptr) {
            st1.push(l1->val);
            l1 = l1->next;
        }
        while(l2 != nullptr) {
            st2.push(l2->val);
            l2 = l2->next;
        }
        int t = 0;
        ListNode* head = nullptr;
        //ListNode* cur = dummy;
        while(!st1.empty() || !st2.empty() || t != 0) {
            if(!st1.empty()) {
                t += st1.top();
                st1.pop();
            }
            if(!st2.empty()) {
                t += st2.top();
                st2.pop();
            }
            ListNode* node = new ListNode(t%10);
            node->next = head;
            head = node;
            t /= 10 ;
        }

        return head;
    }
};

面试题 02.06. 回文链表

编写一个函数,检查输入的链表是否是回文的。

/**
 * 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) {
        //快慢指针找到中点
        ListNode* slow = head;
        ListNode* fast = head;
        int count = 0;
        while(fast != NULL && fast->next != NULL) {
            slow = slow->next;
            fast = fast->next->next;
            count++;
        }
       //翻转后半部分
       ListNode* pre = NULL;
       while(slow != NULL) {
           ListNode* next = slow->next;
           slow->next = pre;
           
           pre = slow;
           slow = next;
       }
       //前后段比较是否一致
       ListNode* node = head;
       while(pre != NULL) {
           if(pre->val != node->val) return false;
           pre = pre->next;
           node = node->next;
       }
        return true;
    }
};

148. 排序链表(☆☆)

给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。
进阶:
你可以在 O(n log 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* sortList(ListNode* head) {
        /*
        使用归并排序的套路,归并排序要求首先需要找到中间点将两个链表一分为二,再分两部分进行递归排序,最后使用合并两个有序链表的方法合并两部分。

        合并两个有序链表可以参考 Leetcode 21 题,由于链表不像数组,数组用新空间存储会造成额外的开销,但是链表仅仅是新建了一个头指针,其余节点都是原地的连接。

        找到链表中间节点可以参考 Leetcode 816题,此处有一点不同的是,不同于数组的归并,找到mid位置后可以使用索引进行分段处理,
        链表必须实实在在的将其分成两部分,故 mid 作为第二条链的头指针, 同时需要维护将第一条链的末尾置为nullptr。

        */
        if(head == nullptr || head->next == nullptr) return head;

        ListNode* mid = middleNode(head);
        ListNode* left = sortList(head);
        ListNode* right = sortList(mid);
        return mergeTwoLists(left, right);
    }


    //寻找中间节点
    ListNode* middleNode(ListNode* head) {
        ListNode* slow = head, *fast = head;
        ListNode* prev = head;

        while(fast && fast->next) {
            fast = fast->next->next;
            prev = slow;
            slow = slow->next;
        }
        prev->next = nullptr;
        return slow;
    }


    //合并两个有序数组
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        /*
        *递归
        *时间复杂度:O(m+n)
        *空间复杂度:O(m+n)
        */
        if(l1 == nullptr)   return l2;
        if(l2 == nullptr)   return l1;
        if(l1->val <= l2->val) {
            l1->next = mergeTwoLists(l1->next,l2);
            return l1;
        } else if(l1->val > l2->val) {
            l2->next = mergeTwoLists(l1,l2->next);
            return l2;
        }
        return l1;
    }
};
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值