算法基础 为什么要学习算法?
算法是内功,决定你武功的高度
算法能让你更好更快理解一门语言系统的设计理念
算法能让你触类旁通
1 、数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func merge (_ nums1 : inout [Int ], _ m : Int , _ nums2 : [Int ], _ n : Int ) { var p1 = m - 1 , p2 = n - 1 var cur = m + n - 1 while (p2 >= 0 ) { if (p1 >= 0 && nums1[p1] >= nums2[p2]) { nums1[cur] = nums1[p1] cur -= 1 ; p1 -= 1 } else { nums1[cur] = nums2[p2] cur -= 1 ; p2 -= 2 } } }
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 func subSort (_ array : [Int ]) -> [Int ] { if array.count < 2 { return [- 1 , - 1 ] } var max = array[0 ], R = - 1 for i in 0 ..< array.count { let v = array[i] if v >= max { max = v } else { R = i } } if R == - 1 { return [- 1 , - 1 ] } var min = array[array.count - 1 ], L = - 1 for i in (0 ... (array.count - 2 )).reversed() { let v = array[i] if v <= min { min = v } else { L = i } } return [L , R ] }
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 func setColors (_ nums : inout [Int ]) { var cur = 0 , left = 0 , right = nums.count - 1 while cur <= right { let curNum = nums[cur] if curNum == 0 { swapNum(& nums, cur, left) cur += 1 left += 1 } else if curNum == 1 { cur += 1 } else { swapNum(& nums, cur, right) right -= 1 } } } func swapNum (_ nums : inout [Int ], _ i : Int , _ j : Int ) { let temp = nums[i] nums[i] = nums[j] nums[j] = temp }
1 2 3 4 5 6 7 8 9 10 public class ListNode {int val;ListNode next; ListNode(int x) { val = x; } }
1 2 3 4 5 6 7 8 9 10 11 12 class Solution {public void deleteNode (ListNode node) {node.val = node.next.val; node.next = node.next.next; } }
题目描述:反转一个单链表。
Solution1: 递归法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public ListNode reverseList (ListNode head) { if (head == null ) return head; if (head.next == null ) return head; ListNode newHead = reverseList(head.next); head.next.next = head; head.next = null ; return newHead; }
Solotion2: 非递归-双指针(https://leetcode-cn.com/problems/reverse-linked-list/solution/fan-zhuan-lian-biao-shuang-zhi-zhen-di-gui-yao-mo-/)
正确示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public ListNode reverseList (ListNode head) {if (head == null ) return head;if (head.next == null ) return head;ListNode newHead = null ;while (head != null ) {ListNode temp = head.next;head.next = newHead; newHead = head; head = temp; } return newHead;}
题目描述:反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。
说明:
1 ≤ m ≤ n ≤ 链表长度。
第一步:找到快慢指针相遇的节点,如果找不到,证明没有环,返回null
第二步:head节出发与slow节点出发,相遇的节点为环的入口节点
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 public boolean hasCycle (ListNode head) {if (head == null || head.next == null ) { return false ; }ListNode fast = head.next;ListNode slow = head;while (fast.next != null && fast.next.next != null ) {slow = slow.next; fast = fast.next.next; if (fast == slow) {return true ;} } return fast = = slow;}
5:环形链表II-寻找环的入口节点
问题描述:给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
说明:不允许修改给定的链表。
官方解读Gif
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 public class KthLargest {private PriorityQueue<Integer> queue;private int limit;public KthLargest (int k, int [] nums) {limit = k; queue = new PriorityQueue <>(k); for (int num : nums) {add(num); } } public int add (int val) {if (queue.size() < limit) {queue.add(val); } else if (val > queue.peek()) { queue.poll(); queue.add(val); } return queue.peek();} }
3、栈和队列 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 func isValid (_ s : String ) -> Bool {var stack: [String ] = []let dic: [String : String ] = [")" : "(" , "}" : "{" , "]" : "[" ]for c in s {let key = String (c)if ! dic.keys.contains(key) {stack.append(key) } else if stack.count == 0 || dic[key] != stack.removeLast() { return false } } return stack.count == 0 }
4、优先队列
Heap (Binary, Binomial, Fibonacci)
Binary Search Tree
Heap Wiki
• https://en.wikipedia.org/wiki/Heap_(data_structure)
5、哈希表 与双指针法 1.1暴力循环
1.2一次哈希思路 标签:哈希映射
这道题本身如果通过暴力遍历的话也是很容易解决的,时间复杂度在 O(n2)
由于哈希查找的时间复杂度为 O(1),所以可以利用哈希容器 map 降低时间复杂度
遍历数组 nums,i 为当前下标,每个值都判断map中是否存在 target-nums[i] 的 key 值
如果存在则找到了两个值,如果不存在则将当前的 (nums[i],i) 存入 map 中,继续遍历直到找到为止
如果最终都没有结果则抛出异常
时间复杂度:O(n)
15. 三数之和 (硅谷面试) 给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
解题思路:
1、可以三重loops循环时间复杂度是O(n3 )
2、可以两层循环得到a+b,然后在Set集合中查找符合-(a+b)的值是否存在,时间复杂度是O(n 2)
3、先排序 后查找:时间复杂度为O(n 2),先排序后得到元素a, 元素b从数组下标1开始,元素c从数组下标array.length开始,检查(a + b + c)的值:
如果(a + b + c)> 0, c–
如果 (a + b + c)< 0, b++
如果(a + b + c) == 0, 得到结果
整个过程时间复杂度是O(N * N)
解题思路3
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 public static List<List<Integer>> threeSum (int [] nums) { List<List<Integer>> ans = new ArrayList (); int len = nums.length; if (nums == null || len < 3 ) return ans; Arrays.sort(nums); for (int i = 0 ; i < len ; i++) { if (nums[i] > 0 ) break ; if (i > 0 && nums[i] == nums[i-1 ]) continue ; int L = i+1 ; int R = len-1 ; while (L < R){ int sum = nums[i] + nums[L] + nums[R]; if (sum == 0 ){ ans.add(Arrays.asList(nums[i],nums[L],nums[R])); while (L<R && nums[L] == nums[L+1 ]) L++; while (L<R && nums[R] == nums[R-1 ]) R--; L++; R--; } else if (sum < 0 ) L++; else if (sum > 0 ) R--; } } return ans; } 作者:guanpengchn 链接:https:
求最小公倍数LCM GCD:最大公约数(Greatest Common Divisor)。指两个或多个整数共有约数中最大的一个。
LCM:最小公倍数(Least Common Multiple)。两个或多个整数公有的倍数叫做它们的公倍数,其中除0以外最小的一个公倍数就叫做这几个整数的最小公倍数。
最小公倍数与最大公约数的关系:**
其中LCM是最小公倍数,GCD是最大公约数。
1 2 3 4 5 6 7 8 func lcm (_ a :Int ,_ b :Int ) -> Int { let num:Int = gcd(a,b) return a * b / num }
所以求最小公倍数的问题可以转化为求最大公约数。
6、二叉树 10道常见题目
DFS与BFS概念
递归法 1、前序遍历 前序遍历递归法:左子树-根节点-右子树
1 2 3 4 5 6 7 8 9 10 11 12 13 func preorderTraversal (_ root : TreeNode ?) -> [Int ] { guard let root = root else { return []} var res: [Int ] = [] res.append(root.val) res += preorderTraversal(root.left) res += preorderTraversal(root.right) return res }
2、中序遍历 中序遍历递归法:左子树-根节点-右子树
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func inorderTraversal (_ root : TreeNode ?) -> [Int ] { guard let root = root else { return [] } var res: [Int ] = [] res += inorderTraversal(root.left) res.append(root.val) res += inorderTraversal(root.right) return res }
3、后序遍历 后序遍历递归法:左子树-右子树-根节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func postorderTraversal (_ root : TreeNode ?) -> [Int ] { guard let root = root else { return [] } var result: [Int ] = [] if let left = root.left { result += postorderTraversal(left) } if let right = root.right { result += postorderTraversal(right) } result.append(root.val) return result }
图片来自网络,侵权删
实现思路:使用队列 1.将根节点入队 2.循环执行以下操作,直到队列为空
将队头节点A出队,进行访问
将A的左子节点入队
将A的右子节点入队
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 func levelOrder (_ root : TreeNode ?) -> [[Int ]] { guard let root = root else { return []} var result: [[Int ]] = [] var queue: [TreeNode ] = [] queue.append(root) while ! queue.isEmpty { var level: [Int ] = [] for _ in 0 ..< queue.count { let node = queue.removeFirst() level.append(node.val) if let left = node.left { queue.append(left) } if let right = node.right { queue.append(right)} } result.append(level) } return result }
从下到上依次返回每一层的结果,解决方案依然是使用队列的先进先出,node入队列的通知,node的左子树、右子树依次入队列,然后用一个temp数组来保存每一层的数值,插入到结果results数组下标位置为0的位置。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func levelOrderBottom (_ root : TreeNode ?) -> [[Int ]] { guard let root = root else { return [] } var results: [[Int ]] = [] var queue: [TreeNode ] = [root] while ! queue.isEmpty { var levelItems: [Int ] = [] for _ in 0 ..< queue.count { let node = queue.removeFirst() levelItems.append(node.val) if let left = node.left { queue.append(left) } if let right = node.right { queue.append(right) } } results.insert(levelItems, at: 0 ) } return results }
自底向上返回层次遍历的值与普通的层次遍历的唯一区别在于每一层的结果levelItems是插入到数组下标为0的位置上,利用swift中数组插入方法insert(levelItems,at: 0)即可。
4.2、迭代法解决前中后序 递归和非递归的区别,无非是一个人为保存现场,一个代码底层自动保存现场。
前序遍历迭代法
利用栈实现 1.将root入栈 2.循环执行以下操作,直到栈为空
弹出栈顶节点top,进行访问
将top.right入栈
将top.left入栈
中序遍历迭代法 利用栈实现 1.设置node=root 2.循环执行以下操作 ✓如果node!=null ✓将node入栈 ✓设置node=node.left ✓如果node==null ✓如果栈为空,结束遍历 ✓如果栈不为空,弹出栈顶元素并赋值给node
➢对node进行访问 ➢设置node=node.right
后序遍历迭代法 ◼利用栈实现 1.将root入栈 2.循环执行以下操作,直到栈为空
首先要明白什么是最大深度:二叉树最大深度是指根节点到最远的叶子节点最长路径上的节点数目
解法一:要求二叉树的最大深度按照递归思想也就是求max(leftHeight, rightHeight) + 1
1 2 3 4 5 6 7 8 9 10 11 12 func maxDepth (_ root : TreeNode ?) -> Int { if let root = root { var leftHeight = 0 , rightHeight = 0 if let left = root.left { leftHeight = maxDepth(left) } if let right = root.right { rightHeight = maxDepth(right) } return max (leftHeight, rightHeight) + 1 } return 0 }
解放二:使用队列,分层遍历DFS,记录层数即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func maxDepth (_ root : TreeNode ?) -> Int {guard let root = root else { return 0 }var depth = 0 var queue: [TreeNode ] = [root]while ! queue.isEmpty { depth += 1 let size = queue.count for _ in 0 ..< size { let node = queue.removeFirst() if let left = node.left { queue.append(left) } if let right = node.right { queue.append(right) } } } return depth
解题思路:求二叉树最大宽度,可以使用分层遍历
使用队列queue来记录每一层入队的结点current
使用数组list来记录当前层所有结点的索引index
将current的左节点入队列同时将其左孩子结点的索引 2 * index放到list中记录
同理current的右孩子结点right入队列queue的同时将右结点索引2 * index + 1放到list数组中记录,这样每一层的索引都被list数组记录
最后用maxLen记录当前层最大深度= list.last! - list.first! + 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 func widthOfBinaryTree (_ root : TreeNode ?) -> Int { guard let root = root else { return 0 } var queue: [TreeNode ] = [root] var list: [Int ] = [1 ] var maxLen = 1 while ! queue.isEmpty { let size = queue.count for _ in 0 ..< size { let node = queue.removeFirst() let index = list.removeFirst() if let left = node.left { queue.append(left) list.append(2 &* index) } if let right = node.right { queue.append(right) list.append(2 &* index &+ 1 ) } } if list.count >= 2 { maxLen = max (maxLen, list.last! &- list.first! &+ 1 ) } } return maxLen } 作者:mingriweiji- github 链接:https: 来源:力扣(LeetCode) 著作权归作者所有。 商业转载请联系作者获得授权,非商业转载请注明出处。
代码实现:
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 guard let root = root else { return 0 } var queue: [(TreeNode , Int )] = [] queue.append((root, 1 )) var maxLen = 1 while ! queue.isEmpty { let count = queue.count for _ in 0 ..< count { let curr = queue.removeFirst() let index = curr.1 if let left = curr.0 .left { queue.append((left, index &* 2 )) } if let right = curr.0 .right { queue.append((right, index &* 2 + 1 )) } } if ! queue.isEmpty { maxLen = max (maxLen, queue.last! .1 &- queue.first! .1 &+ 1 ) } } return maxLen }
1、递归法
递归法解题的关键在于:maxLeft < root.val < minRight
1、临界条件:root==null
2、递归左子树,找到最大值max和根节点root.val进行比较,如果左子树的max大于等于根节点的值,返回false
3、递归右子树,找到右子树的最小值min和根节点root.val比较,如果右子树的min小于等于根节点,返回false
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public boolean helper (TreeNode root, Integer min, Integer max) { if (root == null ) return true ; if (min != null && min > root.val) return false ; if (max != null && max < root.val) return true ; return helper(root.left,min,root.val) && helper(root.right,root.val,max); } public boolean isValidBST (TreeNode root) { return helper(root,null ,null ); }
上面的help()方法不能通过测试用例[1,1]这种重复元素的数组,判断条件错误,修改如下:
Java版本
1 2 3 4 5 6 7 8 9 10 11 public boolean helper (TreeNode root, Integer min, Integer max) { if (root == null ) return true ; if (min != null && min >= root.val) return false ; if (max != null && max <= root.val) return true ; return helper(root.left,root.val,max) && helper(root.right,min,root.val); }
swift版本:注意可选项解包,和判断条件是left.max >= root.val, right.min <= root.val
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 func isValidBST (_ root : TreeNode ?) -> Bool { guard let root = root else { return true } return helper(root,nil ,nil ) } func helper (_ root : TreeNode ?, _ min : Int ?, _ max : Int ?) -> Bool { guard let root = root else { return true } if let min = min, min <= root.val { return false } if let max = max, max >= root.val { return false } return helper(root.left, root.val,max) && helper(root.right, min, root.val) }
解题思路:给定一个二叉树,看它是否镜像对称关键是1、左右子树的值相等 2、左子树的left和右子树的right是镜像对称,利用递归思想容易解决。
24ms代码实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func isSymmetric (_ root : TreeNode ?) -> Bool { return isMirror(root,root) } func isMirror (_ p : TreeNode ?, _ q : TreeNode ?) -> Bool { if p == nil && q == nil { return true } if p == nil || q == nil { return false } return (p! .val == q! .val) && isMirror(p! .left, q! .right) && isMirror(p! .right, q! .left) }
20ms代码实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func isSymmetric (_ root : TreeNode ?) -> Bool { guard let root = root else { return true } return isMirror(root.left, root.right) } func isMirror (_ p : TreeNode ?, _ q : TreeNode ?) -> Bool { if p == nil , q == nil { return true } if let p = p, let q = q, p.val == q.val { return isMirror(p.left, q.right) && isMirror(p.right, q.left) } return false }
第二遍:
执行用时 :16 ms, 在所有 swift 提交中击败了94.96%的用户
内存消耗 :19.9 MB, 在所有 swift 提交中击败了5.00%的用户
1 2 3 4 5 6 7 8 9 10 11 12 13 func isSymmetric (_ root : TreeNode ?) -> Bool { guard let root = root else { return true } return isMirror(root.left, root.right) } func isMirror (_ p : TreeNode ?, _ q : TreeNode ?) -> Bool { if p == nil && q == nil { return true } if let p = p,let q = q, p.val == q.val { return isMirror(p.left, q.right) && isMirror(p.right, q.left) } return false }
迭代就要遍历二叉树,利用分层遍历用队列交换每个结点的左右子树。
8ms迭代法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func invertTree (_ root : TreeNode ?) -> TreeNode ? { guard let root = root else { return nil } var queue: [TreeNode ] = [root] while ! queue.isEmpty { var node = queue.removeFirst() let temp = node.left node.left = node.right node.right = temp if let left = node.left { queue.append(left) } if let right = node.right { queue.append(right) } } return root }
16ms递归法:
1 2 3 4 5 6 7 8 9 10 11 12 13 func invertTree (_ root : TreeNode ?) -> TreeNode ? { guard let root = root else { return nil } let temp = root.left root.left = root.right root.right = temp invertTree(root.left) invertTree(root.right) return root }
8ms递归法:同上面的区别是没有解包,解包耗时8ms?
1 2 3 4 5 6 7 8 9 10 11 12 13 func invertTree (_ root : TreeNode ?) -> TreeNode ? { if root == nil { return nil } let temp = root? .left root? .left = root? .right root? .right = temp invertTree(root? .left) invertTree(root? .right) return root }
第二遍:分层遍历后不要把当前节点放入队列中,我们要的是翻转二叉树,不需要记录当前节点node
7、二叉树搜索树 10道常见题目
8、合并 递归法
1 2 3 4 5 6 7 8 9 10 11 12 13 func mergeTwoLists (_ l1 : ListNode ?, _ l2 : ListNode ?) -> ListNode ? { if l1 == nil { return l2 } if l2 == nil { return l1 } if l1! .val < l2! .val { l1? .next = mergeTwoLists(l1? .next, l2) return l1 } else { l2? .next = mergeTwoLists(l1, l2? .next) return l2 } }
迭代法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 func mergeTwoLists (_ l1 : ListNode ?, _ l2 : ListNode ?) -> ListNode ? { var l1 = l1 var l2 = l2 let head = ListNode (- 1 ) var cur = head while (l1 != nil && l2 != nil ) { if l1! .val <= l2! .val { cur.next = l1 l1 = l1? .next } else { cur.next = l2 l2 = l2? .next } cur = cur.next! } cur.next = l1 == nil ? l2 : l1 return head.next }