算法学习总结

算法学习笔记总结(Java描述)

共XX题,示例代码全部通过

1 数组中的问题其实最常见

– 本节题目数量:16

需要一大张纸和一个笔!!!

常用方法

System.arraycopy(newArr,0,nums,0,n);
//将newArr起始位置赋值到nums[)

1.1 从二分法查找看如何写出正确的程序

二分查找法的思想在1946年提出。

第一个没有bug的二分查找法在1962年才出现

首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。

代码实现:

要明确变量含义

int binarySearch(int arr,int target){
     
	int l = 0;
    int r = arr.length-1;	//区间为[0,n-1];
    int mid;
    while(l<=r){
     //扣一个小边界是等于
        mid = (l+r)/2;
        
        if(arr[min] == target){
     
            return target;
        }else if(target > arr[mid]){
     
            l = mid+1;//在[mid+1,r]
        }else{
     
            r = mid-1;//在[l,mid-1]
        }
    }
    System.out.printf("不存在");
}

1.2 改变变量定义,依然可以写出正确的程序

此时将 l 和 r 分别设置初值为0和arr.length

此时的区间为[o,arr.length)

代码实现:

int binarySearch(int arr,int target){
     
    int l=0;
    int r=arr.length;	//区间为[0,n);
    int mid;
    while(l<r){
     //此时当[x,x),此时x取不到
        mid = (l+r)/2;
        if(arr[mid] == target){
     
			return mid;
        }else if(target > arr[mid]){
     
            l = mid+1;
        }else{
     
            r = mid-1;
        }
    }
    System.out.printf("不存在");
}

小结:

如何写出正确程序

1明确变量含义

2循环不变量

3小数据量调试

1.3 解决第一个问题

T:283 Move Zeros题目链接

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

示例:

输入: [0,1,0,3,12]
输出: [1,3,12,0,0]

思路:用一个新数组暂存

//时间复杂度 O(n)
//空间复杂度 O(n)
class Solution {
     
    public void moveZeroes(int[] nums) {
     
        int n = nums.length;
        int[] newArr = new int[n];
        
        int j=0;
        for(int i=0;i<n;i++){
     
            if(nums[i] != 0){
     
				newArr[j] = nums[i];
                j++;
            }
        }
        System.arraycopy(newArr,0,nums,0,n);
    }
}

1.4 简单的问题也有很多优化思路

双指针遍历使用赋值

在纸上画一画

用两个指针一个快的为r

当r所指的是0就移动;所指的不是0就把这个值赋给l指针;l++;r++;

//时间复杂度 O(n)
//空间复杂度 O(1)
class Solution {
     
    public void moveZeroes(int[] nums) {
     
        int l=0;
         for(int r=0;r<nums.length;r++){
     
             if(nums[r]!=0){
     
                 nums[l] = nums[r];
                 l++;
             }
         }
        for(;l<nums.length;l++){
     
            nums[l] = 0;
        }
    }
}

双指针遍历使用交换,减少空间利用

//时间复杂度 O(n)
//空间复杂度 O(1)
class Solution {
     
    public void moveZeroes(int[] nums) {
     
        int l=0;
         for(int r=0;r<nums.length;r++){
     
             if(nums[r]!=0){
     
                 //nums[l] = nums[r];此处交换两个数就可以了
                 swap(nums,l,r);
                 l++;
             }
         }
    }
    public void swap(int[] arr,int x,int y){
     
        int temp = arr[x];
        arr[x] = arr[y];
        arr[y] = temp;
    }
}

当都不是零的情况细节优化

//当没有零都是数的时候也可以优化

T:27题目链接

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

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

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

示例 1:

给定 nums = [3,2,2,3], val = 3,

函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。

你不需要考虑数组中超出新长度后面的元素。

示例 2:

给定 nums = [0,1,2,2,3,0,4,2], val = 2,

函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。

注意这五个元素可为任意顺序。

你不需要考虑数组中超出新长度后面的元素。

class Solution {
     
    public int removeElement(int[] nums, int val) {
     
        int l=0;
         for(int r=0;r<nums.length;r++){
     
             if(nums[r]!=val){
     
                 swap(nums,l,r);
                 l++;
             }
         }
         return l;
    }
    public void swap(int[] arr,int x,int y){
     
        int temp = arr[x];
        arr[x] = arr[y];
        arr[y] = temp;
    }
}

T: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(int[] nums) {
     
        int l=0;
        Set<Integer> set = new HashSet<>();
        for(int r=0;r<nums.length;r++){
     
             if(!set.contains(nums[r])){
     
                 nums[l] = nums[r];
                 l++;
                 set.add(nums[r]);
             }
         }
        return l;
    }
}

T:80题目链接

给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素最多出现两次,返回移除后数组的新长度。

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

示例 1:

给定 nums = [1,1,1,2,2,3],

函数应返回新长度 length = 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3 。

你不需要考虑数组中超出新长度后面的元素。

示例 2:

给定 nums = [0,0,1,1,1,1,2,3,3],

函数应返回新长度 length = 7, 并且原数组的前五个元素被修改为 0, 0, 1, 1, 2, 3, 3 。

你不需要考虑数组中超出新长度后面的元素。

图解

还是使用快慢指针

慢指针不仅指向自己的位置还指向他的上一个位置

class Solution {
     
    public int removeDuplicates(int[] nums) {
     
        int n = nums.length;
        if(n<=2){
     
            return n;
        }
        int l = 1;
        for(int r=2;r<n;r++){
     
            if(nums[l-1]!=nums[r]){
     
                l++;
                nums[l] = nums[r];
            }
        }
        return l+1;
    }
}

1.5 三路快排partition思路的应用

辅助知识:快速排序

T:75(荷兰国旗问题)题目链接

给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。

注意:
不能使用代码库中的排序函数来解决这道题。

示例:

输入: [2,0,2,1,1,0]
输出: [0,0,1,1,2,2]

思路1标准函数排序

class Solution {
     
    public void sortColors(int[] nums) {
     
         Arrays.sort(nums);
    }
}

思路2三路快排一次partition

三个指针

一个用来扫描,两个用来限定范围

一个限定0的最后一个

一个限定2的第一个

一定要明确变量含义

class Solution {
     
    public void sortColors(int[] nums) {
     
        int l=-1;
        int r=nums.length;
        int i=0;//扫描用
        while(i<l){
     
            if(nums[i]==0){
     
                l++;
                i++;
            }else if(nums[i]==2){
     
                swap(nums,r-1,i);
                r--;
            }else{
     
                i++;
            }
        }
    }
    public void swap(int[] arr,int x,int y){
     
        int temp = arr[x];
        arr[x] = arr[y];
        arr[y] = temp;
    }
}

T:88题目链接

给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。

说明:

初始化 nums1 和 nums2 的元素数量分别为 m 和 n。
你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。

示例:

输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6], n = 3

输出: [1,2,2,3,5,6]

思路1

//简单思路
class Solution {
     
    public void merge(int[] nums1, int m, int[] nums2, int n) {
     
        for(int i=0;m<nums1.length;m++,i++){
     
            nums1[m] = nums2[i];
        }
        Arrays.sort(nums1);

    }
}

思路2

//两个指针从后向前,大的加入
class Solution {
     
    public void merge(int[] nums1, int m, int[] nums2, int n) {
     
        int s1 = m-1;
        int s2 = n-1;
        int s = m+n-1;
        
        while(s1>=0 && s2>=0){
     
            if(nums1[s1]>nums2[s2]){
     
                nums1[s] = nums1[s1];
                s--;
                s1--;
            }else{
     
                nums1[s] = nums2[s2];
                s--;
                s2--;
            }
        }
        System.arraycopy(nums2,0,nums1,0,s2+1);
    }
}

T:215题目链接

在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

示例 1:

输入: [3,2,1,5,6,4] 和 k = 2
输出: 5

示例 2:

输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4

思路1排序直接选

class Solution {
     
    public int findKthLargest(int[] nums, int k) {
     
        Arrays.sort(nums);
        return nums[nums.length-k];
    }
}

思路2快排思想partition

public class Solution {
     

    public int findKthLargest(int[] nums, int k) {
     
        int len = nums.length;
        int left = 0;
        int right = len - 1;

        int target = len - k;

        while (true) {
     
            int index = partition(nums, left, right);
            if (index == target) {
     
                return nums[index];
            } else if (index < target) {
     
                left = index + 1;
            } else {
     
                right = index - 1;
            }
        }
    }

    public int partition(int[] nums, int left, int right) {
     
        int pivot = nums[left];
        int j = left;
        for (int i = left + 1; i <= right; i++) {
     
            if (nums[i] < pivot) {
     
                j++;
                swap(nums, j, i);
            }
        }
        swap(nums, j, left);
        return j;
    }

    private void swap(int[] nums, int index1, int index2) {
     
        int temp = nums[index1];
        nums[index1] = nums[index2];
        nums[index2] = temp;
    }
}

1.6 对撞指针

T:167题目链接

给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。

函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。

说明:

返回的下标值(index1 和 index2)不是从零开始的。
你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。

示例:

输入: numbers = [2, 7, 11, 15], target = 9
输出: [1,2]
解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。

思路1:暴力双层循环


思路2:有序+二分搜索


思路3:对撞指针


附加题目:

T:125题目链接

给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。

说明:本题中,我们将空字符串定义为有效的回文串。

示例 1:

输入: “A man, a plan, a canal: Panama”
输出: true

示例 2:

输入: “race a car”
输出: false


T:344题目链接

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。

不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。

你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。

示例 1:

输入:[“h”,“e”,“l”,“l”,“o”]
输出:[“o”,“l”,“l”,“e”,“h”]

示例 2:

输入:[“H”,“a”,“n”,“n”,“a”,“h”]
输出:[“h”,“a”,“n”,“n”,“a”,“H”]


T:345题目链接

编写一个函数,以字符串作为输入,反转该字符串中的元音字母。

示例 1:

输入: “hello”
输出: “holle”

示例 2:

输入: “leetcode”
输出: “leotcede”


T:11题目链接

给定 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

说明:你不能倾斜容器,且 n 的值至少为 2。

图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。

https://aliyun-lc-upload.oss-cn-hangzhou.aliyuncs.com/aliyun-lc-upload/uploads/2018/07/25/question_11.jpg

示例:

输入: [1,8,6,2,5,4,8,3,7]
输出: 49


也叫双索引技术Two Pointer,

还有一种很常用的是滑动窗口

1.7 滑动窗口

T:209题目链接

给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组。如果不存在符合条件的连续子数组,返回 0。

示例:

输入: s = 7, nums = [2,3,1,2,4,3]
输出: 2
解释: 子数组 [4,3] 是该条件下的长度最小的连续子数组。

思路1:暴力遍历所有连续子数组


思路2:滑动窗口


1.8 在滑动窗口中做记录

T:3题目链接

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:

输入: “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。

示例 2:

输入: “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。

示例 3:

输入: “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。

滑动窗口


T:438问题链接

给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引。

字符串只包含小写英文字母,并且字符串 s 和 p 的长度都不超过 20100。

说明:

字母异位词指字母相同,但排列不同的字符串。
不考虑答案输出的顺序。

示例 1:

输入:
s: “cbaebabacd” p: “abc”

输出:
[0, 6]

解释:
起始索引等于 0 的子串是 “cba”, 它是 “abc” 的字母异位词。
起始索引等于 6 的子串是 “bac”, 它是 “abc” 的字母异位词。

示例 2:

输入:
s: “abab” p: “ab”

输出:
[0, 1, 2]

解释:
起始索引等于 0 的子串是 “ab”, 它是 “ab” 的字母异位词。
起始索引等于 1 的子串是 “ba”, 它是 “ab” 的字母异位词。
起始索引等于 2 的子串是 “ab”, 它是 “ab” 的字母异位词。


T:76题目链接

给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字母的最小子串。

示例:

输入: S = “ADOBECODEBANC”, T = “ABC”
输出: “BANC”


2 查找表相关问题

2.1 set的使用

2.2 map的使用

2.3 set和map的区别

2.4 使用查找表的经典问题

2.5 灵活选择键值1

2.6 灵活选择键值2

2.7 查找表和滑动窗口

2.8 二层搜索树底层实现的顺序性

3 在链表中穿针引线

3.1 链表程序

3.2 设立链表的虚拟头节点

3.3 复杂的穿针引线

3.4 不仅仅是穿针引线

3.5链表与双指针

4 栈、队列、优先队列

于 0 的子串是 “cba”, 它是 “abc” 的字母异位词。

起始索引等于 6 的子串是 “bac”, 它是 “abc” 的字母异位词。

示例 2:

输入:
s: “abab” p: “ab”

输出:
[0, 1, 2]

解释:
起始索引等于 0 的子串是 “ab”, 它是 “ab” 的字母异位词。
起始索引等于 1 的子串是 “ba”, 它是 “ab” 的字母异位词。
起始索引等于 2 的子串是 “ab”, 它是 “ab” 的字母异位词。


T:76题目链接

给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字母的最小子串。

示例:

输入: S = “ADOBECODEBANC”, T = “ABC”
输出: “BANC”


2 查找表相关问题

2.1 set的使用

2.2 map的使用

2.3 set和map的区别

2.4 使用查找表的经典问题

2.5 灵活选择键值1

2.6 灵活选择键值2

2.7 查找表和滑动窗口

2.8 二层搜索树底层实现的顺序性

3 在链表中穿针引线

3.1 链表程序

3.2 设立链表的虚拟头节点

3.3 复杂的穿针引线

3.4 不仅仅是穿针引线

3.5链表与双指针

4 栈、队列、优先队列

你可能感兴趣的:(算法入门,算法,java,递归法,动态规划)