算法—— LeetCode 第一遍

注意本篇博客多为LeetCode中的讨论题解,加上我当时的理解,放在这里是方便自己查看答案。5部分组成:数组,链表,树,动态规划,字符串,共计170道题

一、数组

001 两数之和

class Solution {
   
    public int[] twoSum(int[] nums, int target) {
   
        Map<Integer,Integer> map=new HashMap<>();
        for(int i = 0;i < nums.length;i++){
   
            int complenment=target-nums[i];
            if(map.containsKey(complenment)){
   
                return new int[]{
   map.get(complenment),i};
            }
            map.put(nums[i],i);
        }
         
        throw new IllegalArgumentException("No two sum solution");
    }
}

031 找出下一个数

class Solution {
   
    public void nextPermutation(int[] nums) {
   
        //1、确定 i 的位置
        //从后向前找出开始递减的位置
        int i=nums.length-2;
        //因为和 i + 1 进行比较,找出 i 位置
        while(i>=0&&(nums[i+1]<=nums[i])){
   
            i--;
        }
        //2、与仅仅大于 i 的值交换
        if(i>=0){
   
            int j=nums.length-1;
            while(j>=0&&(nums[j]<=nums[i])){
   
                j--;
            }
            swap(nums,i,j);
        }
        //3、逆序排列后半部分
        reverse(nums,i+1);
    }
    
    //从 i 位置处开始交换
    private void reverse(int[] nums,int start){
   
        int i=start,j=nums.length-1;
        while(i<j){
   
            swap(nums,i,j);
            i++;
            j--;
        }
    }
    
    //交换数组中的两个元素
    private void swap(int[] nums,int i,int j){
   
        int temp=nums[i];
        nums[i]=nums[j];
        nums[j]=temp;
    }
}

033 在 logn 时间内找出指定元素的位置

class Solution {
   
    public int search(int[] nums, int target) {
   
        //判断空数组的情况
        if(nums.length==0){
   
            return -1;
        }
        int lo=0,hi=nums.length-1;
        //迭代实现
        while(lo<hi){
   
            int mid=(lo+hi)/2;
            if(nums[mid]==target){
   
                return mid;
            }
            //分为两种情况
            //1、左端值小于中点值类似于 2,4,5,6,7,0,1 前半部分有序
            if(nums[lo]<=nums[mid]){
   
                //确定是否前半部分有序
                if(target>=nums[lo]&&target<nums[mid]){
   
                    hi=mid-1;
                }else{
   
                    lo=mid+1;
                }
            //2、左端值大于中点值类似于 6,7,0,1,2,4,5 后半部分有序
            }else{
   
                if(target<=nums[hi]&&target>nums[mid]){
   
                    lo=mid+1;
                }else{
   
                    hi=mid-1;
                }
            }
        }
        return nums[lo]==target?lo:-1;
    }
}

034 找出目标值出现在数组中的第一个和最后一个位置;todo:用二分法实现

//两次扫描,O(n) 的复杂度
class Solution {
   
    public int[] searchRange(int[] nums, int target) {
   
        int[] res=new int[]{
   -1,-1};
        if(nums.length==0){
   
            return res;
        }
        //从前到后扫描寻找第一个目标值
        for(int i=0;i<nums.length;i++){
   
            if(nums[i]==target){
   
                res[0]=i;
                //找到就退出循环
                break;
            }
        }
        //如果不含有目标值就不用再进行扫描
        if(res[0]==-1){
   
            return res;
        }
        //从后向前扫描寻找最后一个目标值
        for(int j=nums.length-1;j>=0;j--){
   
            if(nums[j]==target){
   
                res[1]=j;
                break;
            }
        }
        return res;
    }
}

//二分法实现,未弄懂
class Solution {
   
    // returns leftmost (or rightmost) index at which `target` should be
    // inserted in sorted array `nums` via binary search.
    private int extremeInsertionIndex(int[] nums, int target, boolean left) {
   
        int lo = 0;
        int hi = nums.length;

        while (lo < hi) {
   
            int mid = (lo + hi) / 2;
            if (nums[mid] > target || (left && target == nums[mid])) {
   
                hi = mid;
            }
            else {
   
                lo = mid+1;
            }
        }

        return lo;
    }

    public int[] searchRange(int[] nums, int target) {
   
        int[] targetRange = {
   -1, -1};

        int leftIdx = extremeInsertionIndex(nums, target, true);

        // assert that `leftIdx` is within the array bounds and that `target`
        // is actually in `nums`.
        if (leftIdx == nums.length || nums[leftIdx] != target) {
   
            return targetRange;
        }

        targetRange[0] = leftIdx;
        targetRange[1] = extremeInsertionIndex(nums, target, false)-1;

        return targetRange;
    }
}

035 将目标值插入到合适位置

class Solution {
   
    public int searchInsert(int[] nums, int target) {
   
        int lo=0,hi=nums.length-1;
        //注意有等号
        while(lo<=hi){
   
            int mid=(lo+hi)/2;
            if(nums[mid]==target){
   
                return mid;
            }
            else if(target<nums[mid]){
   
                hi=mid-1;
            }else{
   
                lo=mid+1;
            }
        }
        return lo;
    }
}

039 找出所有相加等于目标值的元素,元素可以重复使用,结果数组不可重复

class Solution {
   
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
   
        List<List<Integer>> list=new ArrayList<>();
        Arrays.sort(candidates);
        sum(list,new ArrayList<Integer>(),candidates,target,0);
        return list;
    }
    
    private void sum(List<List<Integer>> list,List<Integer> tempList,int[] candidates,int remain,int start){
   
        if(remain<0){
   
            return;
        }else if(remain==0){
   
            list.add(new ArrayList<>(tempList));
        }else{
   
            //循环遍历数组中的元素,i 从 start 开始,不是从 0 开始(会出现重复数组),递归实现
            for(int i=start;i<candidates.length;i++){
   
                tempList.add(candidates[i]);
                //可以重复利用同一个元素,传入的开始参数为 i 
                sum(list,tempList,candidates,remain-candidates[i],i);
                // tempList.remove(candidates.length-1);
                tempList.remove(tempList.size()-1);
            }
        }
    }
}

040 与 39 题的区别是元素只能被使用一次

class Solution {
   
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
   
        List<List<Integer>> list=new ArrayList<>();
        Arrays.sort(candidates);
        sum(list,new ArrayList<Integer>(),candidates,target,0);
        return list;
    }
    
    private void sum(List<List<Integer>> list,List<Integer> tempList,int[] candidates,int remain,int start){
   
        if(remain<0){
   
            return;
        }else if(remain==0){
   
            list.add(new ArrayList<>(tempList));
        }else{
   
            //循环遍历数组中的元素,i 从 start 开始,不是从 0 开始,递归实现
            for(int i=start;i<candidates.length;i++){
   
                // 跳过同一个元素多次使用的情况
                if(i > start && candidates[i] == candidates[i-1]) continue; 
                tempList.add(candidates[i]);
                //可以重复利用同一个元素,传入的开始参数为 i 
                sum(list,tempList,candidates,remain-candidates[i],i+1);
                // tempList.remove(candidates.length-1);
                tempList.remove(tempList.size()-1);
            }
        }
    }
}

041 找出缺少的第一个最小整数

//大于 n 的整数可以被忽略,因为返回的值范围是 1 到 n + 1;
class Solution {
   
    public int firstMissingPositive(int[] nums) {
   
        int n=nums.length,i=0;
        //这两个循环不等价
        // for(int i=0;i<n;i++){
   
        //     if(nums[i]>=0&&nums[i]<n&&nums[nums[i]]!=nums[i]){
   
        //         swap(nums,i,nums[i]);
        //     }
        // }
        while(i<n){
   
            if(nums[i]>=0&&nums[i]<n&&nums[nums[i]]!=nums[i]){
   
                swap(nums,i,nums[i]);
            }else{
   
                i++;
            }
        }
        //从 1 开始,从 0 开始结果会输出 0,
        int k=1;
        //找到第一个不按规则排列的位置
        while(k<n&&nums[k]==k){
   
            k++;
        }
        //不含元素或者找到上述的位置
        if(n==0||k<n){
   
            return k;
        }else{
   
            //不懂这的含义
            return nums[0]==k?k+1:k;
        }
    }
    
    private void swap(int[] nums,int i,int j){
   
        int temp=nums[i];
        nums[i]=nums[j];
        nums[j]=temp;
    }
}

042 求雨水最大面积

//暴力法,太慢了,动态规划和双指针还不会
class Solution {
   
    public int trap(int[] height) {
   
        int sum=0;
        //注意起始和结束位置
        for(int i=1;i<height.length-1;i++){
   
            int max_left=0,max_right=0;
            //寻找左边的最大值包含 i
            for(int j=i;j>=0;j--){
   
                max_left=Math.max(max_left,height[j]);
            }
            //寻找右边的最大值包含 i
            for(int j=i;j<height.length;j++){
   
                max_right=Math.max(max_right,height[j]);
            }
            sum+=Math.min(max_left,max_right)-height[i];
        }
        return sum;
    }
}

045 跳一跳到最后

//使用贪心算法,每次找到可以跳的最远距离,注意只需统计跳的次数,不需要每次跳的位置
class Solution {
   
    public int jump(int[] nums) {
   
        //当前能跳到的边界,跳到的最远距离,步数
        int end=0,maxPosition=0,steps=0;
        //为什么边界在倒数第二个位置?
        for(int i=0;i<nums.length-1;i++){
   
            //找能跳到最远的,找到后暂时定下,但 i 还在向后循环
            maxPosition=Math.max(maxPosition,nums[i]+i);
            //遇到边界,就更新边界
            if(i==end){
   
                end=maxPosition;
                steps++;
            }
        }
        return steps;
    }
}

048 二维矩阵旋转 90 度

//相当优美的解法
class Solution {
   
    public void rotate(int[][] matrix) {
   
        for(int i=0;i<matrix.length;i++){
   
            //从 i 开始
            for(int j=i;j<matrix[i].length;j++){
   
                int temp=matrix[i][j];
                matrix[i][j]=matrix[j][i];
                matrix[j][i]=temp;
            }
        }
        for(int i=0;i<matrix.length;i++){
   
            //从 0 开始,到二分之一长度结束
            for(int j=0;j<matrix[i].length/2;j++){
   
                int temp=matrix[i][j];
                matrix[i][j]=matrix[i][matrix[0].length-1-j];
                matrix[i][matrix[0].length-1-j]=temp;
            }
        }
    }
}

053 通过动态规划求最大的子数组的和

//加上一个大于 0 的数,数组会变的更大
class Solution {
   
    public int maxSubArray(int[] nums) {
   
        int N=nums.length;
        //以 nums[i] 结束的最大子数组的最大和
        int[] dp=new int[N];
        dp[0]=nums[0];
        int ans=dp[0];
        for(int i=1;i<N;i++){
   
            dp[i]=nums[i]+(dp[i-1]>0?dp[i-1]:0);
            ans=Math.max(ans,dp[i]);
        }
        return ans;
    }
}

054 顺时针循环输出矩阵

class Solution {
   
    public List<Integer> spiralOrder(int[][] matrix) {
   
        //存放结果的容器
        ArrayList<Integer> ans=new ArrayList<>();
        //最先进行非空判断,否则会报空指针异常
        if(matrix.length==0){
   
            return ans;
        }
        //行数
        int R=matrix.length;
        //列数
        int C=matrix[0].length;
        //判断是否已经被访问过的,二维数组
        boolean[][] seen=new boolean[R][C];
        //dr dc 两个数组组成判断行走方向,向右 0 1,向下 1 0,向左 0 -1,向上 -1 0
        int[] dr={
   0,1,0,-1};
        int[] dc={
   1,0,-1,0};
        //当前的行标和列标,di 为方向表示
        int r=0,c=0,di=0;
        //循环的次数为矩阵中元素的个数
        for(int i=0;i<R*C;i++){
   
            ans.add(matrix[r][c]);
            seen[r][c]=true;
            //下一个点的行标和列标
            int cr=r+dr[di];
            int cc=c+dc[di];
            //不需要转向的情况
            if(cr>=0&&cr<R&&cc>=0&&cc<C&&!seen[cr][cc]){
   
                r=cr;
                c=cc;
            }else{
   //需要转向的情况
                //转向分为四种情况通过以下代码处理
                di=(di+1)%4;
                r+=dr[di];
                c+=dc[di];
            }
        }
        return ans;
    }
}

055 判断是否能根据数组从第一个位置跳到最后一个位置

//不能通过,超时,通过动态规划改进
class Solution {
   
    //判断是否能跳到最后一个元素
    public boolean canJump(int[] nums) {
   
        //第一次没写 return
        return canJumpHelper(0,nums);
    }
    
    private boolean canJumpHelper(int position,int[] nums){
   
        //如果位置在最后一个元素,返回真
        if(position==nums.length-1){
   
            return true;
        }
        //能跳到的最远位置,最后一个元素的位置是数组长度减一
        int furthestPosition=Math.min(position+nums[position],nums.length-1);
        //注意边界条件的等号,不然结果会出错
        for(int nextPosition=position+1;nextPosition<=furthestPosition;nextPosition++){
   
            if(canJumpHelper(nextPosition,nums)){
   
                return true;
            }
        }
        return false;
    }
}

057 插入集合元素,可能有重合

/**
 * Definition for an interval.
 * public class Interval {
 *     int start;
 *     int end;
 *     Interval() { start = 0; end = 0; }
 *     Interval(int s, int e) { start = s; end = e; }
 * }
 */
class Solution {
   
    public List<Interval> insert(List<Interval> intervals, Interval newInterval) {
   
        //已经排好序的集合
        List<Interval> result=new LinkedList<>();
        // i 为全局变量
        int i=0;
        //1、第一种情况,老元素的结尾小于新元素的开头
        while(i<intervals.size()&&intervals.get(i).end<newInterval.start){
   
            // i 的自加运算不能丢掉
            result.add(intervals.get(i++));
        }
        //2、第二种情况,新元素的结尾大于老元素的开头,代表元素有重合
        while(i<intervals.size()&&intervals.get(i).start<=newInterval.end){
   
            //将两个元素合并就是创造一个新的元素
            newInterval=new Interval(Math.min(intervals.get(i).start,newInterval.start),
                                     Math.max(intervals.get(i).end,newInterval.end));
            i++;
        }
        //将合并好的元素加入到结果集合中
        result.add(newInterval);
        //3、第三种情况,遍历之后的元素将其加入到结果集合中
        while(i<intervals.size()){
   
            result.add(intervals.get(i++));
        }
        return result;
    }
}

059 循环输入的二维数组

//思路:用上右下左四个变量代表四个方向,根据左右方向大小的不同
class Solution {
   
    public int[][] generateMatrix(int n) {
   
        //输入的 n 代表行数和列数
        int[][] result=new int[n][n];
        int top=0,left=0;
        int down=n-1,right=n-1;
        //存入数组的元素
        int count=1;
        //很精妙,怎么想出来的呢
        while(left<=right&&top<=down){
   
            //从左向右移动
            for(int i=left;i<=right;i++){
   
                result[top][i]=count++;
            }
            top++;
            //从上到下移动
            for(int i=top;i<=down;i++){
   
                result[i][right]=count++;
            }
            right--;
            //从右到左
            for(int i=right;i>=left;i--){
   
                result[down][i]=count++;
            }
            down--;
            //从下到上移动
            for(int i=down;i>=top;i--){
   
                result[i][left]=count++;
            }
            left++;
        }
        return result;
    }
}

062 探索矩阵格子中从起点到终点的路径数目

//利用动态规划的方法
class Solution {
   
    public int uniquePaths(int m, int n) {
   
        int[][] grid=new int[m][n];
        //将第一行置为 1
        for(int i=0;i<n;i++){
   
            grid[0][i]=1;
        }
        //将第一列置为 1
        for(int i=0;i<m;i++){
   
            grid[i][0]=1;
        }
        //利用数学表达式 grid[i][j]=grid[i-1][j]+grid[i][j-1];
        for(int i=1;i<m;i++){
   
            for(int j=1;j<n;j++){
   
                grid[i][j]=grid[i-1][j]+grid[i][j-1];
            }
        }
        return grid[m-1][n-1];
    }
}

063 有障碍物的探索路径数目,障碍用 1 表示

//利用动态规划的方法
class Solution {
   
    public int uniquePathsWithObstacles(int[][] obstacleGrid) {
   
        int m=obstacleGrid.length;
        int n=obstacleGrid[0].length;
        //当第一个位置为 0 时,代表没有路径可走
        if(obstacleGrid[0][0]==1){
   
            return 0;
        }
        
        //如果可以通过,就将初始位置置为 1
        obstacleGrid[0][0]=1;
        
        //将第一行和第一列填满作为初始条件,如果当前数为零并且前一个数为一就填入 1 否则填入 0
        for(int j=1;j<n;j++){
   
            if(obstacleGrid[0][j]==0&&obstacleGrid[0][j-1]==1){
   
                obstacleGrid[0][j]=1;
            }else{
   
                obstacleGrid[0][j]=0;
            }
        }
        for(int i=1;i<m;i++){
   
            //可以通过一个 ? : 表达式进行简化
            if(obstacleGrid[i][0]==0&&obstacleGrid[i-1][0]==1){
   
                obstacleGrid[i][0]=1;
            }else{
   
                obstacleGrid[i][0]=0;
            }
        }
        //利用和上一题相同的递归式
        for(int i=1;i<m;i++){
   
            for(int j=1;j<n;j++){
   
                if(obstacleGrid[i][j]==0){
   
                    obstacleGrid[i][j]=obstacleGrid[i-1][j]+obstacleGrid[i][j-1];
                }else{
   
                    obstacleGrid[i][j]=0;
                }
            }
        }
        return obstacleGrid[m-1][n-1];
    }
}

064 探索路径并求出权重和的最小值

//主要过程和之前两道题一样,都是通过动态规划
class Solution {
   
    public int minPathSum(int[][] grid) {
   
        int m=grid.length;
        int n=grid[0].length;
        for(int i=0;i<m;i++){
   
            for(int j=0;j<n;j++){
   
                if(i==0&&j!=0){
   
                    grid[i][j]=grid[i][j]+grid[i][j-1];
                }else if(j==0&&i!=0){
   
                    grid[i][j]=grid[i][j]+grid[i-1][j];
                }else if(i==0&&j==0){
   
                    grid[i][j]=grid[i][j];
                }else{
   
                    //出错,不是 grid[i][j]=grid[i-1][j]+grid[i][j-1]; 而是加上上一次较小的值
                    grid[i][j]=Math.min(grid[i-1][j],grid[i][j-1])+grid[i][j];
                }
            }
        }
        return grid[m-1][n-1];
    }
}

066 将数组作为整数加 1

class Solution {
   
    public int[] plusOne(int[] digits) {
   
        //分为两种情况,1、当各位数全为 9 时;2、各位数不全为 9 时
        int len=digits.length-1;
        for(int i=len;i>=0;i--){
   
            if(digits[i]<9){
   
                digits[i]++;
                return digits;
            }
            digits[i]=0;
            // digits[i-1]++;不用加,进入 if 作用域自会加
        }
        int[] newDigits=new int[len+2];
        newDigits[0]=1;
        
        return newDigits;
    }
}

073 替换 0 所在行列的数值都为 0

//效率不高
class Solution {
   
    public void setZeroes(int[][] matrix) {
   
        //两次遍历,第一次将要操作的行号和列号存储在 set 集合中,利用 contains 方法再次遍历的时候将其改变
        Set<Integer> rows=new HashSet<>();
        Set<Integer> cols=new HashSet<>();
        int R=matrix.length;
        int C=matrix[0].length;
        //第一次遍历
        for(int i=0;i<R;i++){
   
            for(int j=0;j<C;j++){
   
                if(matrix[i][j]==0){
   
                    rows.add(i);
                    cols.add(j);
                }
            }
        }
        //第二次遍历进行操作
        for(int i=0;i<R;i++){
   
            for(int j=0;j<C;j++){
   
                //只要在那一行或者那一列都进行操作
                if(rows.contains(i)||cols.contains(j)){
   
                    //对遍历到的当前元素进行操作
                    matrix[i][j]=0;
                }
            }
        }
    }
}

074 在有序的二维数组中找出目标值

class Solution {
   
    public boolean searchMatrix(int[][] matrix, int target) {
   
        //疏忽了非空判断
        if(matrix.length==0 || matrix==null || matrix[0].length==0){
   
            return false;
        }
        //按照jianzhioffer 的方法,利用两个指针从右上角开始探索,没有二分查找的效率高
        //行数列数写反了
        int i=0,j=matrix[0].length-1;
        //注意循环的终止条件,边界条件是用行数还是列数
        while(i<matrix.length&&j>=0){
   
            if(matrix[i][j]==target){
   
                return true;
            }else if(matrix[i][j]>target){
   
                j--;
            }else{
   
                i++;
            }
        }
        return false;
    }
}

075 将代表三种颜色数值在数组中进行排序

class Solution {
   
    public void sortColors(int[] nums) {
   
        //1、利用两次遍历的方法
        int count0=0,count1=0,count2=0;
        for(int i=0;i<nums.length;i++){
   
            if(nums[i]==0){
   
                count0++;
            }else if(nums[i]==1){
   
                count1++;
            }else{
   
                count2++;
            }
        }
        for(int i=0;i<nums.length;i++){
   
            if(i<count0){
   
                nums[i]=0;
            }
            //连用 if 语句就不能出正确结果,改成 else 语句就可以了
            else if(i<count0+count1){
   
                nums[i]=1;
            }
            else{
   
                nums[i]=2;
            }
        }
        //TODO: 2、利用依次遍历的方法
    }
}

078 输出一个数组的所有可能得子组合,不能有重复使用的元素

class Solution {
   
    public List<List<Integer>> subsets(int[] nums) {
   
        //正整数数组,找出所有可能的子集合,子集合中不含重复元素;特征是递归和一个临时的列表集合
        //存放结果的容器
        List<List<Integer>> list=new ArrayList<>();
        //对数组进行排序
        Arrays.sort(nums);
        //通过递归回溯的方法解决问题
        backtrack(list,new ArrayList<>(),nums,0);
        return list;
    }
    private void backtrack(List<List<Integer>> list,List<Integer> tempList,int[] nums,int start){
   
        list.add(new ArrayList<>(tempList));
        for(int i=start;i<nums.length;i++){
   
            tempList.add(nums[i]);
            backtrack(list,tempList,nums,i+1);
            //容器的长度计算用的是 size()方法
            //该方法用于保持 t1 的原貌,可以在下一次循环中进行添加其他元素的操作
            tempList.remove(tempList.size()-1);
        }
    }
}
//递归轨迹
list.add
for 0
t1.add
    list.add
    for 1
    t2.add
        list.add
        for 2
        t3.add
        list.add
        t3.rm
    t2.rm
    
    for 2
    t2.add
    list.add
    t2.rm

for 1
t1.add
    list.add
    for 2
    t1.add
        list.add
        for 2
        t1.add
        list.add
        t1.rm
    t1.rm

for 2
t1.add
    list.add
    t1.add
    list.add
    t1.tm
t1.rm

079 找出字母矩阵中是否包含目标单词,从上下左右连续

class Solution {
   
    public boolean exist(char[][] board, String word) {
   
        //找出二维字母组中是否包含相应的单词,字母不能重复利用
        //主要用到了 bit mask 和 dfs 设计算法的方法; 递归方法
        //将字符串转换成字符数组
        char[] w=word.toCharArray();
        //遍历整个二维字母矩阵
        for(int y=0;y<board.length;y++){
   
            for(int x=0;x<board[y].length;x++){
   
                if(exist(board,y,x,w,0)){
   
                    return true;
                }
            }
        }
        return false;
    }
    
    //这就是深度优先搜索的过程
    private boolean exist(char[][] board,int y,int x,char[] word,int i){
   
        //1、到达字符串最后一个字母说明含有这个字符串
        if(i==word.length){
   
            return true;
        }
        //2、超出矩阵的边界说明不含有这个字符串
        if(y<0||x<0||y==board.length||x==board[y].length){
   
            return false;
        }
        //3、出现不相等的情况说明不含对应的字符串,当前的 exist 方法标记为 false
        if(board[y][x]!=word[i]){
   
            return false;
        }
        //利用二进制掩码标记元素是否被访问过,Bit Mask 的原理是什么?
        board[y][x]^=256;
        //对四个方向进行探索,有一个方向符合条件当前 exist 方法标记为 true,一直探索下去,知道有 true 或者 false 的返回为止
        boolean flag=exist(board,y,x+1,word,i+1)
            ||exist(board,y,x-1,word,i+1)
            ||exist(board,y+1,x,word,i+1)
            ||exist(board,y-1,x,word,i+1);
        board[y][x]^=256;
        return flag;
    }
}

080 除去数组中超过两次重复的元素后统计数组的长度

class Solution {
   
    public int removeDuplicates(int[] nums) {
   
        //有序数组,原地操作,允许两次重复,统计出剩余的元素个数
        //有序数组,后面的数一定大于等于前面位置的数
        int i=0;
        //从前到后依次取出数组中的元素
        for(int n:nums){
   
            //前两个数一定能够满足要求,根据数组的有序性,若大于向前数第二个位置的数,则说明不相等,满足要求
            if(i<2||n>nums[i-2]){
   
                nums[i++]=n;
            }
        }
        return i;
    }
}

081 循环的有序数组判断目标值是否包含在数组中

class Solution {
   
    public boolean search(int[] nums, int target) {
   
        int lo=0,hi=nums.length-1;
        while(lo<=hi){
   
            int mid=lo+(hi-lo)/2;
            if(nums[mid]==target){
   
                return true;
            }
            //1、判断是否处于有序的后半部分
            if(nums[mid]<nums[hi]){
   
                if(target>nums[mid]&&target<=nums[hi]){
   
                    lo=mid+1;
                }else{
   
                    hi=mid-1;
                }
            //2、判断是否处于有序的前半部分
            }else if(nums[mid]>nums[hi]){
   
                if(target<nums[mid]&&target>=nums[lo]){
   
                    hi=mid-1;
                }else{
   
                    lo=mid+1;
                }
            //为什么要
            }else{
   
                hi--;
            }
        }
        return false;
    }
}

084

//找出不规则柱状图中的连续最大矩形
//关键点:将所有的高度包括 0 高度入栈;
//栈中存放的是索引; else 作用域中底边长度的判断
class Solution {
   
    public int largestRectangleArea(int[] heights) {
   
        //数组长度
        int len=heights.length;
        //利用栈存放数组的索引
        Stack<Integer> s=new Stack<>();
        //初始化最大面积
        int maxArea=0;
        for(int i=0;i<=len;i++){
   
            //当前位置的高度,当在 len 位置处为 0
            int h=(i==len?0:heights[i]);
            //1、栈中没有元素或者当前高度大于栈顶的索引对应的高度,就将当前高度对应的索引入栈
            if(s.isEmpty()||h>=heights[s.peek()]){
   
                //将索引加入栈中
                s.push(i);
            //2、栈顶高度大于当前高度,将栈顶元素出栈
            }else{
   
                //拿到当前栈顶元素,也就是当前最大的那个元素
                int tp=s.pop();
                // i = 4 时 6 * 4 - 1 - 2  ; 再次进入 5 * 4 - 1 - 1 ,此时栈中只剩索引 1,栈为空时说明之前是递减的数组
                maxArea=Math.max(maxArea,heights[tp]*(s.isEmpty()?i:i-1-s.peek()));
                i--;
            }
        }
        return maxArea;
    }
}

085 ***

//在二进制矩阵中找到由 1 组成的最大矩形的面积
//利用动态规划的方法; 或者联系到 084 题(这里使用的方法)
class Solution {
   
    public int maximalRectangle(char[][] matrix) {
   
        //非空判断
        if(matrix==null||matrix.length==0||matrix[0].length==0){
   
            return 0;
        }
        //行数
        int R=matrix.length;
        //列数
        int C=matrix[0].length;
        //存放高度的数组
        int[] h=new int[C+1];
        h[C]=0;
        //初始化最大面积
        int maxArea=0;
        //循环遍历每一行
        for(int r=0;r<R;r++){
   
            Stack<Integer> s=new Stack<>();
            //遍历每一行中的元素
            for(int c=0;c<=C;c++){
   
                //构造一个和 84 题类似的数组表示柱状图
                if(c<C){
   
                    //分两种情况,当前元素是 0 和当前元素是 1,题目中给的是字符串
                    if(matrix[r][c]=='1'){
   
                        h[c]+=1;
                    }else{
   
                        h[c]=0;
                    }
                }
                if(s.isEmpty()||h[c]>=h[s.peek()]){
   
                    s.push(c);
                }else{
   
                    //与之前的不同之处
                    while(!s.isEmpty()&&h[c]<h[s.peek()]){
   
                        int tp=s.pop();
                        maxArea=Math.max(maxArea,h[tp]*(s.isEmpty()?c:c-1-s.peek()));
                    }
                    //与之前的不同之处
                    s.push(c);
                }
            }
        }
        return maxArea;
    }
}

//动态规划的方法未理解

088

//与归并算法中的实现相同
//nums1 = [1,2,3,0,0,0], m = 3,注意这里 m 的初试值为 3 不是 6
//nums2 = [2,5,6],       n = 3
class Solution {
   
    public void merge(int[] nums1, int m, int[] nums2, int n) {
   
        //定义两个数组都是从尾部开始遍历,组成的新数组的长度为两个数组长度相加,索引都是长度减一
        int i=m-1,j=n-1,k=m+n-1;
        while(i>=0&&j>=0){
   
            nums1[k--]=nums1[i]>nums2[j]?nums1[i--]:nums2[j--];
        }
        while(j>=0){
   
            nums1[k--]=nums2[j--];
        }
    }
}

090

//列出所有数组所可能包含的子数组的情况
//所给数组中包含重复元素,结果集合中不能包含重复的集合元素
//与前一个类似的题目唯一不同之处在于需要跳过原数组中的重复元素
class Solution {
   
    public List<List<Integer>> subsetsWithDup(int[] nums) {
   
        List<List<Integer>> list=new ArrayList<>();
        Arrays.sort(nums);
        backtrack(list,new ArrayList<>(),nums,0);
        return list;
    }
    private void backtrack(List<List<Integer>> list,List<Integer> tempList,int[] nums,int start){
   
        list.add(new ArrayList<>(tempList));
        for(int i=start;i<nums.length;i++){
   
            //跳过重复元素
            if(i>start&&nums[i]==nums[i-1]){
   
                continue;
            }
            tempList.add(nums[i]);
            backtrack(list,tempList,nums,i+1);
            tempList.remove(tempList.size()-1);
        }
    }
}

105

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
//通过给出的前序遍历和中序遍历结果构造二叉树,假设树中不存在重复元素
//pre[0] 是根节点,中序数组中 pre[0] 左边的为根的左子树,右边同理;迭代处理子数组就能得到原始的树
//两个数组的左右字数对应的索引起始位置是相同的
class Solution {
   
    public TreeNode buildTree(int[] preorder, int[] inorder) {
   
        return buildTree(0,0,inorder.length-1,preorder,inorder);
    }
    //前序数组的第一个元素;中序数组的第一个元素;中序数组的最后一个元素;前序数组;中序数组
    private TreeNode buildTree(int preStart,int inStart,int inEnd,int[] preorder,int[] inorder){
   
        //传入的两个数组为空, 并为叶子结点赋值为 null
        //如果 inEnd 用 inorder.length 代替会报越界异常??
        if(preStart>preorder.length-1||inStart>inEnd){
   
            return null;
        }
        //定义根节点
        TreeNode root=new TreeNode(preorder[preStart]);
        //初始化根节点在中序遍历数组中的索引位置
        int inIndex=0;
        //边界条件丢掉了等号导致 StackOverflowError 错误
        //注意这里的循环起点和终点是根据递归变化的,不是 0 和最后一位
        for(int i=inStart;i<=inEnd;i++){
   
            if(inorder[i]==root.val){
   
                inIndex=i;
            }
        }
        //主要问题是确定新的左右子树的索引位置
        //左子树的根等于当前根的索引加一
        root.left=buildTree(preStart+1,inStart,inIndex-1,preorder,inorder);
        //关键是根据中序遍历的规律确定第一个参数也就是根节点的在前序遍历中的索引
        //右子树的根等于左子树的根加上左子树所含的节点数目
        root.right=buildTree(preStart+inIndex-inStart+1,inIndex+1,inEnd,preorder,inorder);
        return root;
    }
}

106

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
//根据中序和后序输入数组构造二叉树
class Solution {
   
    public TreeNode buildTree(int[] inorder, int[] postorder) {
   
        //后续数组的遍历开始点与之前的前序遍历有区别
        return buildTree(postorder.length-1,0,inorder.length-1,postorder,inorder);
    }
    private TreeNode buildTree(int postStart,int inStart,int inEnd,int[] postorder,int[] inorder){
   
        if(postStart>postorder.length-1||inStart>inEnd){
   
            return null;
        }
        TreeNode root=new TreeNode(postorder[postStart]);
        int inIndex=0;
        for(int i=inStart;i<=inEnd;i++){
   
            if(inorder[i]==root.val){
   
                inIndex=i;
            }
        }
        //对于第一次操作 4 - (4 - 1) - 1 = 0 左子树的根节点
        //不要丢掉括号
        //左子树的根节点位置是右子树根节点减去右子树的节点数目
        root.left=buildTree(postStart-(inEnd-inIndex)-1,inStart,inIndex-1,postorder,inorder);
        //右子树的根节点是父节点索引位置减一
        root.right=buildTree(postStart-1,inIndex+1,inEnd,postorder,inorder);
        return root;
    }
}

118

//两侧元素都是 1 ,输入的是二维矩阵的行数
class Solution {
   
    public List<List<Integer>> generate(int numRows) {
   
        //一个存放结果的集合
        List<List<Integer>> list=new ArrayList<>();
        //1、第一种基本情况,输入为 0 时,结果为空,非空判断
        if(numRows==0){
   
            return list;
        }
        //2、第二种易得的情况,第一行永远为 1,先添加一行,再将这一行的第一个元素设置为 1
        //先加一行,再取出来放入元素 1
        list.add(new ArrayList<>());
        list.get(0).add(1);
        //从第二行(也就是行索引为 1 的位置)开始,总行数已知
        //嵌套循环利用表达式将集合元素赋值
        for(int r=1;r<numRows;r++){
   
            //存放当前行数据的集合, 新建
            //对每一行进行处理
            List<Integer> nowRow=new ArrayList<>();
            //存放前一行数据的集合,从总集合中获得
            //前面已经给第一行初始化了,所以可以直接取到第一行集合的值
            List<Integer> preRow=list.get(r-1);
            //每一行的第一个元素总是 1
            //循环取到每一行元素的时候将其第一个元素赋值为 1
            nowRow.add(1);
            //根据题意列表达式,每一行的元素数目等于当前行号
            for(int c=1;c<r;c++){
   
                nowRow.add(preRow.get(c)+preRow.get(c-1));
            }
            //最后一个元素设为 1 ,将行集合加入到总集合中
            nowRow.add(1);
            list.add(nowRow);
        }
        return list;
    }
}

119

//注意标号是从 0 开始,输入行号,返回那一行的数组
class Solution {
   
    public List<Integer> getRow(int rowIndex) {
   
        List<Integer> list=new ArrayList<>();
        //后面的方法可以处理索引为 0 的情况
        if(rowIndex<0){
   
            return list;
        }
        //循环相加到指定索引所在的行,边界条件很重要
        for(int i=0;i<=rowIndex;i++){
   
            //add 和 set 方法用反了
            list.add(0,1);
            //循环到倒数第二个位置结束
            for(int j=1;j<list.size()-1;j++){
   
                //用 set 方法替换而不是用 add 向后叠加
                //注意将哪两个索引位置处的数据相加
                //注意 +1
                list.set(j,list.get(j)+list.get(j+1));
                //不用再最后位置加 1 了
            }
        }
        return list;
    }
}

120

//从下向上依次求出相邻的较小值叠加
//需要一个额外的数组存放数据
class Solution {
   
    public int minimumTotal(List<List<Integer>> triangle) {
   
        //因为最后用到了第长度位置处的元素
        int[] result=new int[triangle.size()+1];
        for(int i=triangle.size()-1;i>=0;i--){
   
            for(int j=0;j<triangle.get(i).size();j++){
   
                result[j]=Math.min(result[j],result[j+1])+triangle.get(i).get(j);
            }
        }
        return result[0];
    }
}

121

//返回数组中的从后到前的最大差值
class Solution {
   
    public int maxProfit(int[] prices) {
   
        int maxProfit=0;
        int len=prices.length;
        // i 到最后第二个位置为止
        for(int i=0;i<len-1;i++){
   
            for(int j=i+1;j<len;j++){
   
                int nowProfit=prices[j]-prices[i];
                if(nowProfit>maxProfit){
   
                    maxProfit=nowProfit;
                }
            }
        }
        return maxProfit;
    }
}

//通过初始化最大值和最小值达到遍历一遍数组就得到结果的效果
public class Solution {
   
    public int maxProfit(int prices[]) {
   
        int minprice = Integer.MAX_VALUE;
        //注意设置的是最大利润,而不是最大值
        int maxprofit = 0;
        for (int i = 0; i < prices.length; i++) {
   
            if (prices[i] < minprice)
                minprice = prices[i];
            else if (prices[i] - minprice > maxprofit)
                maxprofit = prices[i] - minprice;
        }
        return maxprofit;
    }
}

122

//可以进行多次交易的求最大利润
//将所有分段的从波峰到波谷的差值相加
//关键点是检查曲线的单调性
class Solution {
   
    public int maxProfit(int[] prices) {
   
        //未进行非空判断
        if(prices==null||prices.length==0){
   
            return 0;
        }
        int i=0;
        int vallay=prices[0];
        int peak=prices[0];
        int maxProfit=0;
        //倒数第二个位置为止,在这出错了
        while(i<prices.length-1){
   
            //寻找波谷, 找到开始递减的位置,下一个位置就是波谷
            //不是和波峰或者波谷比较,是当前元素和下一个元素比较
            while(i<prices.length-1&&prices[i]>=prices[i+1]){
   
                i++;
            }
            vallay=prices[i];
            //寻找波峰,开始递增的下一个位置为波峰
            while(i<prices.length-1&&prices[i]<=prices[i+1]){
   
                i++;
            }
            peak=prices[i];
            maxProfit+=peak-vallay;
        }
        return maxProfit;
    }
}

123 ***

//最多进行两次交易,求交易能够获得的最大利润
//网格 dp 中存放的是有几段元素时当前的最大利润,k 代表行号,i 代表列号
//不卖,最大利润和前一天相等,在 j 天买入并在 i 天卖出的利润最大为 prices[i] - prices[j] + dp[k-1, j-1]
//对于本例来说画一个 3 行 7 列的表格
class Solution {
   
    public int maxProfit(int[] prices) 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
KMP算法是一种字符串匹配算法,用于在一个文本串S内查找一个模式串P的出现位置。它的时间复杂度为O(n+m),其中n为文本串的长度,m为模式串的长度。 KMP算法的核心思想是利用已知信息来避免不必要的字符比较。具体来说,它维护一个next数组,其中next[i]表示当第i个字符匹配失败时,下一次匹配应该从模式串的第next[i]个字符开始。 我们可以通过一个简单的例子来理解KMP算法的思想。假设文本串为S="ababababca",模式串为P="abababca",我们想要在S中查找P的出现位置。 首先,我们可以将P的每个前缀和后缀进行比较,得到next数组: | i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | | --- | - | - | - | - | - | - | - | - | | P | a | b | a | b | a | b | c | a | | next| 0 | 0 | 1 | 2 | 3 | 4 | 0 | 1 | 接下来,我们从S的第一个字符开始匹配P。当S的第七个字符和P的第七个字符匹配失败时,我们可以利用next[6]=4,将P向右移动4个字符,使得P的第五个字符与S的第七个字符对齐。此时,我们可以发现P的前五个字符和S的前五个字符已经匹配成功了。因此,我们可以继续从S的第六个字符开始匹配P。 当S的第十个字符和P的第八个字符匹配失败时,我们可以利用next[7]=1,将P向右移动一个字符,使得P的第一个字符和S的第十个字符对齐。此时,我们可以发现P的前一个字符和S的第十个字符已经匹配成功了。因此,我们可以继续从S的第十一个字符开始匹配P。 最终,我们可以发现P出现在S的第二个位置。 下面是KMP算法的C++代码实现:

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值