练习题手撕总结

基础篇
1.基础知识(时间复杂度、空间复杂度等)
2.线性表(顺序表、单链表)
3.双链表、循环链表
4.队列
5.栈
6.递归算法
7.树、二叉树(递归、非递归遍历)
8.二叉搜索树(BST)
9.二分查找
10.二叉平衡树(AVL)
提高篇
1.散列表(哈希表)
2.堆
3.哈夫曼树、哈夫曼编码
4.并查集
5.串(KMP算法)
6.分治算法
7.贪心算法
8.回溯算法
9.动态规划(常见LCS、LIS等)
图论必会算法
1.DFS深度优先搜索
2.BFS广度优先搜索
3.Dijkstra(单源)最短路径算法
4.Floyd(多源)最短路径算法
5.Prim、Kruskal最小生成树算法
6.拓扑排序、关键路径
排序必知算法
1.交换类:冒泡排序
2.交换类:快速排序
3.插入类:直接插入排序
4.插入类:希尔排序
5.选择类:简单选择排序
6.选择类:堆排序
7.归并排序
8.桶排序
9.计数排序
10.基数排序
高级数据结构
1.红黑树
2.B树
3.跳跃表
4.字典树(Trie)
5.树状数组
6.后缀数组和后缀树
7.线段树
笔试面试常遇
1.快速幂
2.大数加减乘除
3.位运算
4.欧几里得(GCD)最大公约数、最小公倍数
5.滑动窗口、双指针
6.约瑟夫环问题
7.求素数(素数筛)
8.回文串(马拉车算法)

时间复杂度、空间复杂度

空间复杂度计算

数组中的数之和

3个不同值的数字和为target,只需输出一组符合数组

import java.util.Arrays;

public class Main {
    public static int[] threeSum(int[] nums, int target) {
        Arrays.sort(nums); // 先对数组排序
        int n = nums.length;
        // 遍历数组,固定第一个数
        for (int i = 0; i < n - 2; i++) {
            // 跳过重复的数字
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            int left = i + 1; // 第二个数的下标
            int right = n - 1; // 第三个数的下标
            while (left < right) {
                int sum = nums[i] + nums[left] + nums[right];
                if (sum == target) {
                    return new int[] { nums[i], nums[left], nums[right] };
                } else if (sum < target) {
                    left++;
                } else {
                    right--;
                }
            }
        }
        // 如果不存在符合条件的三个数,则返回空数组
        return new int[] {};
    }

    public static void main(String[] args) {
        int[] nums = { -1, 0, 1, 2, -1, -4 };
        int target = 0;
        int[] result = threeSum(nums, target);
        if (result.length == 3) {
            System.out.println("输出:" + Arrays.toString(result));
        } else {
            System.out.println("没有找到符合条件的三个数");
        }
    }
}

任意数量的可重复值数字和为target,需要输出所有符合数组

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static List<List<Integer>> combinationSum(int[] nums, int target) {
        List<List<Integer>> result = new ArrayList<>();
        backtrack(nums, target, 0, new ArrayList<>(), result);
        return result;
    }

    private static void backtrack(int[] nums, int target, int start, List<Integer> current, List<List<Integer>> result) {
        if (target == 0) {
            result.add(new ArrayList<>(current));
            return;
        }

        for (int i = start; i < nums.length; i++) {
            if (target - nums[i] >= 0) {
                current.add(nums[i]);
                backtrack(nums, target - nums[i], i, current, result);
                current.remove(current.size() - 1);
            }
        }
    }

    public static void main(String[] args) {
        int[] nums = {2, 3, 6, 7};
        int target = 7;
        List<List<Integer>> result = combinationSum(nums, target);
        // System.out.println("输出:");
        for (List<Integer> combination : result) {
            System.out.println(combination);
        }
    }
}

任意数量的子串数组和为target,需要统计所有符合数组数量

输入:nums = [1,1,1], k = 2
输出:2

public class Solution {
    public int subarraySum(int[] nums, int k) {
        int count = 0;
        for (int start = 0; start < nums.length; ++start) {
            int sum = 0;
            for (int end = start; end >= 0; --end) {
                sum += nums[end];
                if (sum == k) {
                    count++;
                }
            }
        }
        return count;
    }
}

最大子数组和

你找出一个具有最大和的连续子数组,子数组是数组中的一个连续部分
输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续了数组 [4,1,2,1] 的和最大,为 6 

class Solution {
    public int maxSubArray(int[] nums) {
        int pre = 0, maxAns = nums[0];
        for (int x : nums) {
            pre = Math.max(pre + x, x);
            maxAns = Math.max(maxAns, pre);
        }
        return maxAns;
    }
}

3个不同位置数和为0(值可能重复),需要输出所有符合数组

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。注意,输出的顺序和三元组的顺序并不重要。
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
解法:用了双指针

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        int n = nums.length;
        Arrays.sort(nums);
        List<List<Integer>> ans = new ArrayList<List<Integer>>();
        // 枚举 a
        for (int first = 0; first < n; ++first) {
            // 需要和上一次枚举的数不相同,原来保证数据不一致就这啊
            if (first > 0 && nums[first] == nums[first - 1]) {
                continue;
            }
            // c 对应的指针初始指向数组的最右端
            int third = n - 1;
            int target = -nums[first];
            // 枚举 b
            for (int second = first + 1; second < n; ++second) {
                // 需要和上一次枚举的数不相同
                if (second > first + 1 && nums[second] == nums[second - 1]) {
                    continue;
                }
                // 需要保证 b 的指针在 c 的指针的左侧
                while (second < third && nums[second] + nums[third] > target) {
                    --third;
                }
                // 如果指针重合,随着 b 后续的增加
                // 就不会有满足 a+b+c=0 并且 b<c 的 c 了,可以退出循环
                if (second == third) {
                    break;
                }
                if (nums[second] + nums[third] == target) {
                    List<Integer> list = new ArrayList<Integer>();
                    list.add(nums[first]);
                    list.add(nums[second]);
                    list.add(nums[third]);
                    ans.add(list);
                }
            }
        }
        return ans;
    }
}

哈希Map、List

字母异位词

给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。字母异位词 是由重新排列源单词的所有字母得到的一个新单词。
输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]

解法:由于互为字母异位词的两个字符串包含的字母相同,因此对两个字符串分别进行排序之后得到的字符串一定是相同的,故可以将排序之后的字符串作为哈希表的键。

class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        Map<String, List<String>> map = new HashMap<String, List<String>>();
        for (String str : strs) {
            char[] array = str.toCharArray();
            Arrays.sort(array);
            String key = new String(array);//排序后的结果为该字符串的key

            List<String> list = map.getOrDefault(key, new ArrayList<String>());
//getOrDefault从map中获取与由key索引得到的对应值,这个值存了之前key索引下的字符串列表,如果找到则将其赋值给变量list
//如果没有找到,则创建一个新的空列表并将其赋值给list,这个不影响输出

            list.add(str);
//在这个字符串列表里加入该字符串

            map.put(key,list);
//如果key在map中已经存在,将更新后的列表list覆盖旧的list。
//如果 key 在 map 中不存在,则会添加新的键值对。

        }
        return new ArrayList<List<String>>(map.values());
    }
}

双指针

判断链表是否有环

1、定义两个指针,一个快指针(每次移动两步),一个慢指针(每次移动一步),初始时都指向链表的头节点。
2、使用一个循环来遍历链表,每次循环中快指针移动两步,慢指针移动一步。
3、如果链表中存在环,则快指针和慢指针最终会相遇;如果链表中不存在环,则快指针会先到达链表尾部,此时可以判断链表无环。
4、当快指针和慢指针相遇时,说明链表中存在环。

public class Solution {
    public boolean hasCycle(ListNode head) {
        if (head == null || head.next == null) {
            return false;
        }
        
        ListNode slow = head;
        ListNode fast = head.next;
        
        while (slow != fast) {
            if (fast == null || fast.next == null) {
                return false;
            }
            slow = slow.next;
            fast = fast.next.next;
        }
        
        return true;
    }
}

所有 0 移动到数组的末尾

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。请注意 ,必须在不复制数组的情况下原地对数组进行操作。
输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]

解法:两个指针一前一后(从左开始遍历到右,前就是右)。右指针识别为0数,右指针动左指针不动;右指针识别为非0数,该数放到左指针,然后两个指针一起动;最后根据左指针位置补齐0

class Solution {
	public void moveZeroes(int[] nums) {
		if(nums==null) {
			return;
		}
		//两个指针i和j
		int j = 0;
		for(int i=0;i<nums.length;i++) {
			//当前元素!=0,就把其交换到左边,等于0的交换到右边
			if(nums[i]!=0) {
				int tmp = nums[i];
				nums[i] = nums[j];
				nums[j++] = tmp;
			}
		}
	}
}	

盛最多水的容器

找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。返回容器可以储存的最大水量。

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

双指针i,j分列水槽左右两端,选定两板高度中的较短板,向中间收窄一格,直至双指针相遇时跳出;更新面积最大值 res,最后返回面积最大值

class Solution {
    public int maxArea(int[] height) {
        int i = 0, j = height.length - 1, res = 0;
        while(i < j) {
            res = height[i] < height[j] ? 
                Math.max(res, (j - i) * height[i++]): 
                Math.max(res, (j - i) * height[j--]); 
        }
        return res;
    }
}

滑动窗口

最小覆盖字串

大字符串str1以及一个独立的小字符串str2,从这个大字符串str1里找到包含独立小字符串str2中所有字符的最小子字符串str3。例如,大字符串"meituan2019"和一个子字符串"i2t”,答案就应该是"ituan2”

滑动窗口代码模板

import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class Main {

    public static String findMinimumSubstring(String str1, String str2) {
        if (str2 == null || str2.isEmpty()) {
            return str1;
        }//处理特殊情况

        Map<Character, Integer> targetMap = new HashMap<>();
        for (char ch : str2.toCharArray()) {
            targetMap.put(ch, targetMap.getOrDefault(ch, 0) + 1);
        }
//构建目标映射,对于每个字符ch,如果该字符还没有在 targetMap 中出现过,返回默认值 0;
//如果该字符已经在 targetMap 中存在,则返回其出现的次数,然后将这个次数加1

        int left = 0, right = 0;//窗口的左右边界
        int minLen = Integer.MAX_VALUE;
        int count = str2.length();//窗口中满足条件的字符个数
        int startIndex = 0;//最小子字符串的起始位置

        Map<Character, Integer> windowMap = new HashMap<>();
        while (right < str1.length()) {
            char ch = str1.charAt(right);
            if (targetMap.containsKey(ch)) {//windowMap指窗口内字符的出现次数
                windowMap.put(ch, windowMap.getOrDefault(ch, 0) + 1);
                if (windowMap.get(ch).intValue() <= targetMap.get(ch).intValue()) {
                    count--;
                }
            }

            while (count == 0) {
                if (right - left + 1 < minLen) {
                    minLen = right - left + 1;
                    startIndex = left;
                }
                char leftChar = str1.charAt(left);
                if (targetMap.containsKey(leftChar)) {
                    windowMap.put(leftChar, windowMap.get(leftChar) - 1);
                    if (windowMap.get(leftChar).intValue() < targetMap.get(leftChar).intValue()) {
                        count++;
                    }
                }
                left++;
            }
            right++;
        }
//在每一步中,窗口右移一格,将字符加入窗口,并更新 windowMap 和 count。
//当 count 减为0时,表示当前窗口包含了 str2 中的所有字符,开始尝试缩小窗口。
//每次左移窗口时,更新最小子字符串的长度和起始位置。

        return minLen == Integer.MAX_VALUE ? "" : str1.substring(startIndex, startIndex + minLen);
//如果找到了最小子字符串,则返回该子字符串;否则返回空字符串

    }

    public static void main(String[] args) {
        // String str1 = "meituan2019";
        // String str2 = "i2t";
        Scanner in = new Scanner(System.in);
        String str1 = in.nextLine();
        String str2 = in.nextLine();

        System.out.println(findMinimumSubstring(str1, str2));
    }
}

无重复字符的最长子串

给定一个字符串 s,请你找出其中不含有重复字符的 最长子串 的长度
输入: s ="abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

class Solution {
    public int lengthOfLongestSubstring(String s) {
    char[] arr = s.toCharArray();
    int left = 0;
    int right = 0;
    int maxLen = 0;

    HashSet<Character> set = new HashSet<>();
    while (right < arr.length) {
        char ch = arr[right];
        if (!set.contains(ch)) {
            set.add(ch);
            right++;
            maxLen = Math.max(maxLen, set.size());
        } else {
            set.remove(arr[left]);
            left++;
        }
    }
    return maxLen;
}

}

子串是指定串的异位词

给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。
输入: s = "cbaebabacd", p = "abc"
输出: [0,6]
解释:起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词。

class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        int sLen = s.length(), pLen = p.length();

        if (sLen < pLen) {
            return new ArrayList<Integer>();
        }//异常值排查,相当于返回null

        List<Integer> ans = new ArrayList<Integer>();
        int[] sCount = new int[26];//sCount 用于记录目标字符串中的字符出现次数
        int[] pCount = new int[26];//pCount 用于记录字母异位词中的字符出现次数。

        for (int i = 0; i < pLen; ++i) {
            ++sCount[s.charAt(i) - 'a'];
            ++pCount[p.charAt(i) - 'a'];
        } //把字符串转成数组,s是S字符串的前plength位

        if (Arrays.equals(sCount, pCount)) {
            ans.add(0);
        }//首值排查,当两个字符串一样

        for (int i = 0; i < sLen - pLen; ++i) {
            --sCount[s.charAt(i) - 'a'];
            ++sCount[s.charAt(i + pLen) - 'a'];
//对于每个窗口,减少窗口左边界字符的计数,并增加窗口右边界字符的计数,这样就将窗口滑动到下一个位置
//意思就是s是个和p同长的滑块,从左往右算每个plength长的滑块符不符合

            if (Arrays.equals(sCount, pCount)) {//符合就添加当前位置
                ans.add(i + 1);
            }
        }

        return ans;
    }
}

滑动窗口中的最大值

滑动窗口中的最大值

找出数字连续的最长序列

不要定性思维,比如下题

给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。

输入:nums = [100,4,200,1,3,2]
输出:4
import java.util.HashSet;

class Solution {
    public int longestConsecutive(int[] nums) {
        if (nums == null || nums.length == 0) return 0;

        HashSet<Integer> set = new HashSet<>();
        for (int num : nums) {
            set.add(num);
        }//将数组中的所有元素存入集合中,以便快速判断一个数字是否在数组中

        int longest = 0;
        for (int num : nums) {
            if (!set.contains(num - 1)) { // 如果当前数字的前一个数字不在集合中,说明当前数字是一个连续序列的起点
                int currentNum = num;
                int currentLength = 1;

                while (set.contains(currentNum + 1)) { // 向后查找连续的数字
                    currentNum++;
                    currentLength++;
                }

                longest = Math.max(longest, currentLength); // 更新最长连续序列的长度
            }
        }

        return longest;
    }
}

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值