数据结构与算法: 算法篇: 大数相加: 两数之和:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 class Solution { public ListNode addTwoNumbers (ListNode l1, ListNode l2) { ListNode head = null ; ListNode tail = null ; int carry=0 ; while (l1!=null ||l2!=null ){ int val1 = l1 != null ? l1.val : 0 ; int val2 = l2 != null ? l2.val : 0 ; int sum = val1+ val2 + carry; if (head ==null ){ head = tail = new ListNode(sum%10 ); } else { tail.next = new ListNode(sum%10 ); tail= tail.next; } carry = sum/10 ; if (l1!=null ){ l1= l1.next; } if (l2!=null ){ l2=l2.next; } } if (carry>0 ){ tail.next= new ListNode(carry); } return head; } }
需要注意的是结点循环开始是当前结点是不是空,最后,如果当前结点不为空,那么就进行添加到尾结点
回溯:
求子集与求组和不同的是,子集进行横向遍历的时候不需要将之前使用的值参加组合,组合每次横向都是从0开始,然后使用userd记录值是否已经使用过,子集每次从上一次结束偏移一个单位。
子集(元素无重不可复选)
迭代法: 复制上一次的结果集,加入新的元素,并将新增的元素加入到结果集中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public List<List<Integer>> subsets(int [] nums) { List<List<Integer>> result = new ArrayList<>(); result.add(new ArrayList<Integer>()); for (int num : nums){ List<List<Integer> > newSubsets = new ArrayList<>(); for (List<Integer> subsets: result){ List<Integer> subset = new ArrayList<>(subsets); subset.add(num); newSubsets.add(subset); } result.addAll(newSubsets); } return result; } }
回溯方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { List<List<Integer>> res= new LinkedList<>(); LinkedList<Integer> trace= new LinkedList<>(); public List<List<Integer>> subsets(int [] nums) { backTrack(nums,0 ); return res; } public void backTrack (int [] nums,int start) { res.add(new LinkedList<>(trace)); for (int i =start;i<nums.length;i++){ trace.addLast(nums[i]); backTrack(nums,i+1 ); trace.removeLast(); } } }
全排列问题
递归进行深度遍历,循环进行横向遍历,使用一个链表进行路径保存,一个数组进行元素使用标记,递归结束的条件就是遍历到树的底部时候路径长度是否与给定的长度一致。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 class Solution { List<List<Integer>> res = new LinkedList<>(); public List<List<Integer>> permute(int [] nums) { LinkedList<Integer> track = new LinkedList<>(); boolean used[] = new boolean [nums.length]; backTrack(nums,track,used); return res; } public void backTrack (int nums[],LinkedList<Integer> track,boolean used[]) { if (track.size()==nums.length){ res.add(new LinkedList(track)); return ; } for (int i =0 ;i<nums.length;i++){ if (used[i]){ continue ; } track.add(nums[i]); used[i]=true ; backTrack(nums,track,used); track.removeLast(); used[i]=false ; } } } result = [] def backtrack (路径, 选择列表) : if 满足结束条件: result.add (路径) return for 选择 in 选择列表: 做选择 backtrack (路径, 选择列表) 撤销选择
子集Ⅱ
相当于从第一个元素开始
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution { List<List<Integer>> res = new LinkedList<>(); LinkedList<Integer> track = new LinkedList<>(); public List<List<Integer>> subsetsWithDup(int [] nums) { Arrays.sort(nums); baskTrack(nums,0 ); return res; } public void baskTrack (int [] nums,int start) { res.add(new LinkedList<>(track)); for (int i=start;i<nums.length;i++){ if (i>start&&nums[i]==nums[i-1 ]){ continue ; } track.addLast(nums[i]); baskTrack(nums,i+1 ); track.removeLast(); } } }
组合(元素无重不可复选):
就是把所有的组合数目求解出来,在添加进数组中我们限制添加进去的条件,判断子集是否是所需要的长度
遍历方式和求解子集的元素是一样的。一个集合记录最后 的元素的值,一个记录当前的路径,在递归中进行横向遍历,进入下一层,去除当前元素,进入递归后,从trace退出一个元素,进行横向遍历。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution { List<List<Integer>> res = new LinkedList<>(); LinkedList<Integer> track = new LinkedList<>(); public List<List<Integer>> combine(int n, int k) { int [] nums = new int [n] ; for (int i=0 ;i<n;i++){ nums[i]=i+1 ; } backTrack(nums,0 ,k); return res; } public void backTrack (int [] nums,int start,int k) { if (track.size()==k){ res.add(new LinkedList<>(track)); return ; } for (int i=start;i<nums.length;i++){track.addLast(nums[i]); backTrack(nums,i+1 ,k); track.removeLast(); } } }
需要进行剪枝操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Solution { List<List<Integer>> res = new LinkedList<>(); LinkedList<Integer> track = new LinkedList<>(); boolean []used; public List<List<Integer>> permuteUnique(int [] nums) { Arrays.sort(nums); used = new boolean [nums.length]; backTrack(nums); return res; } public void backTrack (int []nums) { if (track.size()==nums.length){ res.add(new LinkedList<>(track)); return ; } for (int i=0 ;i<nums.length;i++){ if (used[i]){ continue ; } if (i>0 &&nums[i]==nums[i-1 ]&&!used[i-1 ]){ continue ; } track.addLast(nums[i]); used[i]=true ; backTrack(nums); track.removeLast(); used[i]=false ; } } }
排序 动态规划:
使用递归(超时): 1 2 3 4 5 6 7 8 9 10 11 12 public int fib (int n) { final int MOD = 1000000007 ; if (n==0 ){ return 0 ; } if (n==1 ){ return 1 ; } int sum = (fib(n-1 )+fib(n-2 ))%MOD; return sum; }
因为使用递归带有大量的重复计算,所以预先将已经计算好的存储在HashMap中。
带记忆的递归 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Map<Integer,Integer> map = new HashMap(); public int fib (int n) { final int MOD = 1000000007 ; if (n<2 ){ return n; } if (map.containsKey(n)){ return map.get(n); } int sum = (fib(n-1 )+fib(n-2 ))%MOD; map.put(n,sum); return sum; }
使用动态规划: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public int fib (int n) { Integer num = 1000000007 ; if (n<2 ){ return n; } int dp[] = new int [n+1 ]; dp[0 ]=0 ; dp[1 ]=1 ; for (int i=2 ;i<=n;i++){ int sum =dp[i-1 ]+dp[i-2 ]; dp[i] =sum %num; } return dp[n]; } }
优化空间之后的动态规划 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public int fib (int n) { Integer num = 1000000007 ; if (n<2 ){ return n; } int sum = 0 ; int a=0 ; int b=1 ; for (int i=2 ;i<=n;i++){ sum =(a+b)%num; a= b; b= sum; } return sum; } }
爬楼梯问题 // 倒推思想,我走到第四个台阶,有几种走法,可以从第三走一个,可以从第二走两个,依次往下,可以知道,
f(n)=f(n-1)+f(n-2)
利用hash表存储,计算过的数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { Map<Integer,Integer> memo= new HashMap<>(128 ); final Integer num = 1000000007 ; public int numWays (int n) { if (n<=1 ){ return 1 ; } if (memo.containsKey(n) ){ return memo.get(n); } int a = numWays(n-1 )%num; memo.put(n-1 ,a); int b= numWays(n-2 )%num; memo.put(n-2 ,b); int sum = (a+b)%num; memo.put(n-2 ,sum); return sum; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public int numWays (int n) { final int nums = 1000000007 ; if (n<=1 ){ return 1 ; } int a =1 ; int b= 1 ; int sum =0 ; for (int i=2 ;i<=n;i++){ sum = (a+b)%nums; a= b; b= sum; } return sum; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 class Solution { public ListNode mergeTwoLists (ListNode l1, ListNode l2) { ListNode res = new ListNode(0 ); ListNode cur= res; res.next=null ; ListNode temp ; while (l1!=null &&l2!=null ){ if (l1.val<=l2.val){ res.next=l1; l1=l1.next; }else { res.next=l2; l2=l2.next; } res=res.next; } if (l1==null && l2!=null ){ res.next = l2; } if (l1!=null && l2==null ){ res.next = l1; } return cur.next; } }
优先级 队列(PriorityQueue)(40条消息) Java基本数据结构——优先级队列(堆)_1485Lucifer的博客-CSDN博客
(40条消息) 【Java每日一题】Java笔试100题(1)_1485Lucifer的博客-CSDN博客_java每日一题
树的遍历:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public List<Integer> preorder (Node root) { List<Integer> res = new ArrayList<>(); helper(root,res); return res; } public void helper (Node root,List<Integer> res) { if (root ==null ){ return ; } res.add(root.val); for (Node ch:root.children){ helper(ch,res); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 class Solution { public List<List<Integer>> levelOrder(Node root) { if (root == null ) { return new ArrayList<List<Integer>>(); } List<List<Integer>> ans = new ArrayList<List<Integer>>(); Queue<Node> queue = new ArrayDeque<Node>(); queue.offer(root); while (!queue.isEmpty()) { int cnt = queue.size(); List<Integer> level = new ArrayList<Integer>(); for (int i = 0 ; i < cnt; ++i) { Node cur = queue.poll(); level.add(cur.val); for (Node child : cur.children) { queue.offer(child); } } ans.add(level); } return ans; } }
双指针: 双指针技巧秒杀七道数组题目 :: labuladong 的算法小抄
可以使用双指针:右指针 right 指向当前将要处理的元素,左指针 left 指向下一个将要赋值的位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public int removeDuplicates (int [] nums) { int slow = 0 ; int fast = 0 ; while (fast < nums.length) { if (nums[slow]!=nums[fast]) { slow++; nums[slow] = nums[fast]; } fast++; } return slow + 1 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 class Solution { public ListNode deleteDuplicates (ListNode head) { if (head==null ){ return null ; } ListNode slow = head; ListNode fast = head; while (fast!=null ){ if (slow.val!=fast.val){ slow.next = fast; slow=slow.next; } fast=fast.next; } slow.next=null ; return head; } }
关于 slow++ 时机: 如果使用 slow 先 ++,那么当第一个元素是所需要的元素是,会筛选不掉
错误代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public int removeElement (int [] nums, int val) { int slow =0 ; int fast =0 ; while (fast<nums.length){ if (nums[fast]!=val){ slow++; nums[slow]=nums[fast]; } fast++; } return slow+1 ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public int removeElement (int [] nums, int val) { int slow =0 ; int fast =0 ; while (fast<nums.length){ if (nums[fast]!=val){ nums[slow]=nums[fast]; slow++; } fast++; } return slow; } }
二分查找: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public int search (int [] nums, int target) { int left =0 ; int right = nums.length-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 ; } }
前者相当于两端都闭区间 [left, right],后者相当于左闭右开区间 [left, right),因为索引大小为 nums.length 是越界的。 那什么时候应该停止,?就是搜索为空的时候,
while (left <= right) 的终止条件是 left == right + 1,写成区间的形式就是 [right + 1, right],或者带个具体的数字进去 [3, 2],可见这时候区间为空,因为没有数字既大于等于 3 又小于等于 2 的吧。所以这时候 while 循环终止是正确的,直接返回 -1 即可。
while (left < right) 的终止条件是 left == right,写成区间的形式就是 [right, right],或者带个具体的数字进去 [2, 2],这时候区间非空,还有一个数 2,但此时 while 循环终止了。也就是说这区间 [2, 2] 被漏掉了,索引 2 没有被搜索,如果这时候直接返回 -1 就是错误的。
day06 - 二分查找 - 力扣(LeetCode)
在排序数组中查找数字 I 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 统计一个数字在排序数组中出现的次数。 JAVA class Solution { public int search (int [] nums, int target) { int len = nums.length; if (len == 0 ) { return 0 ; } int firstPosition = left_bound(nums, target); if (firstPosition == -1 ) { return 0 ; } int lastPosition = right_bound(nums, target); return lastPosition - firstPosition +1 ; } int left_bound (int [] nums, int target) { int left = 0 , right = nums.length - 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 if (nums[mid] == target) { right = mid - 1 ; } } if (left == nums.length) return -1 ; return nums[left] == target ? left : -1 ; } int right_bound (int [] nums, int target) { int left = 0 , right = nums.length - 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 if (nums[mid] == target) { left = mid + 1 ; } } if (left - 1 < 0 ) return -1 ; return nums[left - 1 ] == target ? (left - 1 ) : -1 ; } }
我写了首诗,让你闭着眼睛也能写对二分搜索 :: labuladong 的算法小抄