数组中的第K个最大元素
快速选择算法:首选定义个辅助函数实现交换,partition 比轴值小的放左边,初始化最左边 遍历数组调整轴值位置 把轴值放到预期位置 quickselect 定义递归出口 随机生成轴值 调用 partion函数 调用 quickselect
无重复字符的最长子串
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。 输入: s = "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc",所以其
长度为 3。
第一种方法:暴力解法 逐个生成子字符串 看它是否不含有重复的字符 时间复杂度 Hash set o(n) 第二种方法:滑动窗口及优化
class Solution { public int lengthOfLongestSubstring(String s) { // 哈希集合,记录每个字符是否出现过 Set<Character> occ = new HashSet<Character>(); int n = s.length(); // 右指针,初始值为 -1,相当于我们在字符串的左边界的左侧,还没有开始移动 int rk = -1, ans = 0; for (int i = 0; i < n; ++i) { if (i != 0) { // 左指针向右移动一格,移除一个字符 occ.remove(s.charAt(i - 1)); } while (rk + 1 < n && !occ.contains(s.charAt(rk + 1))) { // 不断地移动右指针 occ.add(s.charAt(rk + 1)); ++rk; } // 第 i 到 rk 个字符是一个极长的无重复字符子串 ans = Math.max(ans, rk - i + 1); } return ans; } }
手撕快速排序
给你一个整数数组 `nums`,请你将该数组升序排列。
输入:nums = [5,2,3,1] 输出:[1,2,3,5]
class Solution { public int[] sortArray(int[] nums) { randomizedQuicksort(nums, 0, nums.length - 1); return nums; } public void randomizedQuicksort(int[] nums, int l, int r) { if (l < r) { int pos = randomizedPartition(nums, l, r); randomizedQuicksort(nums, l, pos - 1); randomizedQuicksort(nums, pos + 1, r); } } public int randomizedPartition(int[] nums, int l, int r) { int i = new Random().nextInt(r - l + 1) + l; // 随机选一个作为我们的主元 swap(nums, r, i); return partition(nums, l, r); } public int partition(int[] nums, int l, int r) { int pivot = nums[r]; int i = l - 1; for (int j = l; j <= r - 1; ++j) { if (nums[j] <= pivot) { i = i + 1; swap(nums, i, j); } } swap(nums, i + 1, r); return i + 1; } private void swap(int[] nums, int i, int j) { int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; } }
4.合并两个有序链表:
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 方法一 递归我们可以的如下递归地定义两个链表里merge 操作 也就是说,两个链表头部值较小的一个节点与剩下元素的merge 操作结果合并。 我们直接将以上递归过程建模,同时需要考虑边界情况。如果l1与l2一开始就是空俩链表,那么没有任何操作需要合并,所以我们只需要返回非空链表,否则,我们要判断L1和L2那一个链表的头节点的值更小,然后递归地决定下一个添加到结果里的节点,如果两个链表有一个为空 ,递归结束
class Solution { public ListNode mergeTwoLists(ListNode l1, ListNode l2) { if (l1 == null) { return l2; } else if (l2 == null) { return l1; } else if (l1.val < l2.val) { l1.next = mergeTwoLists(l1.next, l2); return l1; } else { l2.next = mergeTwoLists(l1, l2.next); return l2; } } }
-
两数之和 刷完这些题,你将会得到:
-
一个刷题套路:先暴力解法,然后优化,再优化,最后追求程序的极致性能
-
三个优化算法的应用:二分查找、哈希查找和双指针技巧
先暴力解法
// 时间复杂度:O(n^2) // 空间复杂度:O(1) public int[] twoSum(int[] nums, int target) { if (nums == null || nums.length == 0) return new int[0];
for (int i = 0; i < nums.length; i++) { int x = nums[i]; // 线性查找 - O(n) for (int j = i + 1; j < nums.length; j++) { if (nums[j] == target - x) { return new int[]{i, j}; } } }
return new int[0]; }
二分查找优化:慢在线性查找o(n)的情况二分查找时间复杂度是o(logN);
哈希查找 时间复杂度更低 是O(1) 先构成一张哈希表,Hashset(integer) et = new HashSet《》()
简单: 1.爬楼梯假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。 输入: 2 输出: 2 解释: 有两种方法可以爬到楼顶。
-
1 阶 + 1 阶
-
2 阶 动态规划的代码 我们用 f(x)表示爬到第 xx 级台阶的方案数,考虑最后一步可能跨了一级台阶,也可能跨了两级台阶,所以我们可以列出如下式子: 代码:
链表题的常见思路:定义一个新的链表指针,所以我们定义一个pre 指针
然后我们定义个cur指针用来标识遍历到head的那个位置
然后开始遍历链表,进行反转
然后我们需要注意的就是定义一个next指针来指向cur的下一个节点这个指针的含义就是防止链表断开而造成链表信息的丢失。
反转链表:
class Solution { public ListNode reverseList(ListNode head) { if(head ==null || head.next ==null) return head; // null->1->2->3->4->5->null // p c ListNode prev = null; ListNode curr = head; 保存头节点,以防止头结点丢失 while(curr !=null){ // null->1->2->3->4->5->null // p c n ListNode next = curr.next; // null<-1 2->3->4->5->null // p c n curr.next = prev; // null<-1 2->3->4->5->null // c n // p prev = curr; // null<-1 2->3->4->5->null // p n // c curr = next; } // null<-1<-2<-3<-4<-5 null // p n // c return prev; } }
有效括号:国际站?
class Solution { public boolean isValid(String s) { Stack<Character> stack = new Stack<Character>(); for (char c : s.toCharArray()) { if (c == '(') stack.push(')'); else if (c == '{') stack.push('}'); else if (c == '[') stack.push(']'); else if (stack.isEmpty() || stack.pop() != c) return false; } return stack.isEmpty(); } }
数组中的第K大元素:(理解)
class Solution { public int findKthLargest(int[] nums, int k) { • int low = 0, high = nums.length -1; • while(low <= high){ • int index = low-1; • for(int i = low; i < high; i++){ • if(nums[i] > nums[high]){ • swap(nums, i, ++index); • } • } • swap(nums, ++index, high); • if(index == k - 1){ • return nums[index]; • } • if(index < k -1){ • low = index + 1; • }else{ • high = index - 1; • } • } • return -1; } private void swap(int[] nums, int a, int b){ • int temp = nums[a]; • nums[a] = nums[b]; • nums[b] = temp; } }
最长无重复字符的子串(my)
思路:用一个hashmap记录每个字母的index 如果这个字母已经在map里了 说明已经有重复了 这样就更新看这个字母上次出现的index
需要注意的是这种情况:“bacbca” 这里的a上次出现的index比c上次出现的index早 如果按a上次出现的index字符串就变成: cbca 这就有重复的 所以: i = max(i, preIndex(a)) 这样就能保证是不重复的字符串
class Solution { public int lengthOfLongestSubstring(String s) { int i = 0; Map<Character, Integer> map = new HashMap<>(); int maxLen = 0; for (int j = 0; j < s.length(); j++) { if (map.containsKey(s.charAt(j))) { i = Math.max(i, map.get(s.charAt(j)) + 1); } map.put(s.charAt(j), j); maxLen = Math.max(maxLen, j - i + 1); } return maxLen; } }
LRU缓存机制
为最近最少使用(LRU)缓存策略设计一个数据结构,它应该支持以下操作:获取数据和写入数据。
-
get(key)
获取数据:如果缓存中存在key,则获取其数据值(通常是正数),否则返回-1。 -
set(key, value)
写入数据:如果key还没有在缓存中,则设置或插入其数据值。当缓存达到上限,它应该在写入新数据之前删除最近最少使用的数据用来腾出空闲位置。
最终, 你需要返回每次 get
的数据.
手撕快速排序
my尾部的零 easy 设计一个算法计算出n阶乘中尾部零的个数。
class Solution { public long trailingZeros(long n) { long sum = 0; while (n != 0) { sum += n / 5; n /= 5; } return sum; } };
my 二叉树的层次遍历easy
输入:{1,2,3} 输出:[[1],[2,3]] 解释: 1 / \ 2 3 它将被序列化为{1,2,3} 层次遍历
两数之和:给一个整数数组,找到两个数使得他们的和等于一个给定的数 target。
public class Solution { public int[] twoSum(int[] numbers, int target) { //用一个hashmap来记录,key记录target-numbers[i]的值,value记录numbers[i]的i的值,如果碰到一个 //numbers[j]在hashmap中存在,那么说明前面的某个numbers[i]和numbers[j]的和为target,i和j即为答案 HashMap<Integer,Integer> map = new HashMap<>(); for (int i = 0; i < numbers.length; i++) { if (map.get(numbers[i]) != null) { int[] result = {map.get(numbers[i]), i}; return result; } map.put(target - numbers[i], i); } int[] result = {}; return result; } }
合并k个排序链表并且返回合并后的排序链表。尝试分析和描述其复杂度。
判断一个链表是不是带环?
快慢指针的经典题。 快指针每次走两步,慢指针一次走一步。 在慢指针进入环之后,快慢指针之间的距离每次缩小1,所以最终能相遇。
public class Solution { public Boolean hasCycle(ListNode head) { if (head == null || head.next == null) { return false; } ListNode fast, slow; fast = head.next; slow = head; while (fast != slow) { if(fast==null || fast.next==null) return false; fast = fast.next.next; slow = slow.next; } return true; } }
最大子序和(最大子数组)和
给定一个整数数组 nums
,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
输入:nums = [-2,1,-3,4,-1,2,1,-5,4] 输出:6 解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
因此可以利用贪心思想,通过局部的子数组最大,进而得到整体的最优解
-
定义 $maxAns$ 记录全局最大值,即结果;$sum$ 记录当前子数组的和;
-
初始化: $maxAns=Integer.MIN_VALUE; \ sum=0; $ 因为数组可以全为负数,因此$maxAns$不能直接初始化为0;
-
遍历整数数组:
-
$sum$累加当前的值;
-
若当前 $sum>maxAns$ ,更新 $maxAns=sum$;
-
若当前 $sum<0$ ,表示当前的子数组和已经为负,累加到后面会使和更小,因此令 $sum=0$,相当于放弃当前的子数组,重新开始;
-
public class Solution { public int maxSubArray(int[] nums) { // maxAns记录全局最大值 sum记录当前子数组的和 int maxAns = Integer.MIN_VALUE, sum = 0; // 贪心 for (int i = 0; i < nums.length; i++) { sum += nums[i]; maxAns = Math.max(maxAns, sum); sum = Math.max(sum, 0); } return maxAns; } }
-
public class Solution { public int maxSubArray(int[] nums) { if (nums == null || nums.length ==0) return 0; int max = nums[0]; for (int i=1; i<nums.length; i++) { nums[i] = Math.max(nums[i], nums[i-1]+nums[i]); max = Math.max(max, nums[i]); } return max; } }
平衡二叉树
在树上做一次DFS,记录以每个点为根的子树高度解法2中提供了更简洁的方法,将子树高度作为返回值返回
public class Solution { public boolean isBalanced(TreeNode root) { return maxDepth(root) != -1; } private int maxDepth(TreeNode root) { if (root == null) { return 0; } int left = maxDepth(root.left); int right = maxDepth(root.right); if (left == -1 || right == -1 || Math.abs(left-right) > 1) { return -1; } return Math.max(left, right) + 1; } }
删除排序数组中的重复数字
输入: [1,1,2] 输出: 2 解释: 数字只出现一次的数组为: [1,2]
由于有序,所以相同的数字排在一起。 用一个游标变量指向已经去重的部分的下一个空位,只要$ai != ai-1$,就将ai填入之前的空位。
public class Solution { public int removeDuplicates(int[] A) { if (A == null || A.length == 0) { return 0; } int size = 0; for (int i = 0; i < A.length; i++) { if (A[i] != A[size]) { A[++size] = A[i]; } } return size + 1; } }
移动零
给一个数组 nums 写一个函数将 0
移动到数组的最后面,非零元素保持原数组的顺序
输入: nums = [0, 1, 0, 3, 12], 输出: [1, 3, 12, 0, 0].
遍历 不为零的放里面 为零的放后面 就这样就完了啊
public class Solution { public void moveZeroes(int[] nums) { // write your code here if(nums == null || nums.length == 0) return; int i = 0; for(int num: nums) { if(num != 0) nums[i++] = num; } while(i < nums.length){ nums[i++] = 0; } } }
使用双指针,其实由于置换元素的特殊性(等于0),所以其实不需要单独存个tmp 时间复杂度:O(n); 空间复杂度:常数2
public class Solution { public void moveZeroes(int[] nums) { int fast = 0, slow = 0; while(fast < nums.length){ if(nums[fast] != 0){ if(nums[slow] == 0){ nums[slow] = nums[fast]; nums[fast] = 0; } slow++; } fast++; } } }
二叉树的前序遍历 (递归)
public class Solution { public ArrayList<Integer> preorderTraversal(TreeNode root) { ArrayList<Integer> result = new ArrayList<Integer>(); if (root == null) { return result; //结束条件 } ArrayList<Integer> left = preorderTraversal(root.left); ArrayList<Integer> right = preorderTraversal(root.right); result.add(root.val); result.addAll(left); result.addAll(right); return result; } }
二叉树的中序遍历
public class Solution { public List<Integer> inorderTraversal(TreeNode root) { List<Integer> result = new ArrayList<Integer>(); if(root == null){ return result; } List<Integer> left = inorderTraversal(root.left); List<Integer> right = inorderTraversal(root.right); result.addAll(left); result.add(root.val); result.addAll(right); return result; } }
尾随零
给定一个整数n
,返回n!
(n的阶乘)的尾随零的个数。
输入: n = 5 输出: 1 解释: 1*2*3*4*5=120
public class Solution { public int trailingZeroes(int n) { return n < 5 ? 0 :trailingZeroes(n/5) + n/5; } }class Solution { public int trailingZeroes (int n) { if (n >= 5) { return n / 5 + trailingZeroes (n / 5); } else { return 0; } } }
合并两个排序链表
将两个排序(升序)链表合并为一个新的升序排序链表
public ListNode mergeTwoLists(ListNode l1, ListNode l2) { if (l1 == null) { return l2; } else if (l2 == null) { return l1; } else if (l1.val < l2.val) { l1.next = mergeTwoLists(l1.next, l2); return l1; } else { l2.next = mergeTwoLists(l1, l2.next); return l2; } }
链表倒数第n个节点
找到单链表的倒数第n个节点,保证链表中节点的最少数量为n。
public class Solution { ListNode nthToLast(ListNode head, int n) { if (head == null || n < 1) { return null; } ListNode p1 = head; ListNode p2 = head; for (int j = 0; j < n - 1; ++j) { if (p2 == null) { return null; } p2 = p2.next; } while (p2.next != null) { p1 = p1.next; p2 = p2.next; } return p1; } }