剑指offer个人算法准备
剑指Offer
数据结构
链表双指针
JZ6 从尾到头打印链表
翻转,浪费时间
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
| import java.util.*;
import java.util.ArrayList; public class Solution { public ArrayList<Integer> printListFromTailToHead(ListNode listNode) { ArrayList<Integer>result = new ArrayList<>();
ListNode last = null;
while (listNode != null) { ListNode next = listNode.next; listNode.next = last; last = listNode; if (next != null) { listNode = next; } else { break; } }
while (listNode != null) { result.add(listNode.val); listNode = listNode.next; }
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
| import java.util.*;
import java.util.ArrayList; public class Solution { public ArrayList<Integer> printListFromTailToHead(ListNode listNode) { ArrayList<Integer>result = new ArrayList<>();
while(listNode!=null){ result.add(0,listNode.val); listNode = listNode.next; }
return result; } }
|
JZ24 反转链表
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
| import java.util.*;
public class Solution {
public ListNode ReverseList (ListNode head) { ListNode last = null; while (head != null) { ListNode next = head.next; head.next = last; last = head; if (next != null) { head = next; } else { break; } } return head; } }
|
JZ25 合并两个排序的链表
迭代
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
| import java.util.*;
public class Solution {
public ListNode Merge (ListNode pHead1, ListNode pHead2) { if (pHead1 == null) { return pHead2; } else if (pHead2 == null) { return pHead1; } ListNode head = null; if (pHead1.val < pHead2.val) { head = pHead1; pHead1 = pHead1.next; } else { head = pHead2; pHead2 = pHead2.next; } ListNode temp = head; while (pHead1 != null && pHead2 != null) { if (pHead1.val < pHead2.val) { temp.next = pHead1; pHead1 = pHead1.next; } else { temp.next = pHead2; pHead2 = pHead2.next; } temp = temp.next; } temp.next = (pHead1 != null) ? pHead1 : pHead2; return head; } }
|
递归
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
| import java.util.*;
public class Solution {
public ListNode Merge (ListNode pHead1, ListNode pHead2) { if (pHead1 == null) { return pHead2; } else if (pHead2 == null) { return pHead1; } if(pHead1.val<pHead2.val){ pHead1.next = Merge(pHead1.next,pHead2); return pHead1; }else{ pHead2.next = Merge(pHead1,pHead2.next); return pHead2; } } }
|
JZ52 两个链表的第一个公共结点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import java.util.*;
public class Solution { public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) { ListNode t1 = pHead1, t2 = pHead2; while (t1 != t2) { t1 = (t1 == null) ? pHead2 : t1.next; t2 = (t2 == null) ? pHead1 : t2.next; } return t1; } }
|
JZ23 链表中环的入口结点
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
| import java.util.*;
public class Solution {
public ListNode EntryNodeOfLoop(ListNode pHead) { ListNode fast = pHead, slow = pHead, slow_real = null; while (fast != null && fast.next != null) { fast = fast.next.next; slow = slow.next; if (fast == slow) { slow_real = slow; break; } } if (slow_real == null) { return null; } fast = pHead; while (fast != slow_real) { fast = fast.next; slow_real = slow_real.next; } return slow_real; } }
|
JZ22 链表中倒数最后k个结点
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
| import java.util.*;
public class Solution {
public ListNode FindKthToTail (ListNode pHead, int k) { if (pHead == null) { return null; } ListNode fast = pHead; for (int i = 0; i < k; i++) { if (i != k - 1 && fast.next == null) { return null; } fast = fast.next; } ListNode slow = pHead; while (fast != null) { slow = slow.next; fast = fast.next; } return slow; } }
|
JZ35 复杂链表的复制
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
| import java.util.*;
public class Solution { public RandomListNode Clone(RandomListNode pHead) { if (pHead == null) { return null; } RandomListNode newHead = pHead; while (newHead != null) { RandomListNode clone = new RandomListNode(newHead.label); clone.next = newHead.next; newHead.next = clone; newHead = clone.next; } newHead = pHead; RandomListNode clone = pHead.next; while (newHead != null) { if (newHead.random == null) { clone.random = null; } else { clone.random = newHead.random.next; } newHead = newHead.next.next; if (clone.next != null) { clone = clone.next.next; } } RandomListNode result = pHead.next; newHead = pHead.next; while (pHead != null) { pHead.next = pHead.next.next; pHead = pHead.next; if (newHead.next != null) { newHead.next = newHead.next.next; } newHead = newHead.next; } return result; } }
|
JZ76 删除链表中重复的结点
难得递归直接写出来
就是每次都判断当前的开头是不是不同的,不同就处理下一个,返回当前的,相同就不断排除掉相同的,递归处理下一个,下一个可能相同,可能不同,可能为null,交给下一次递归处理,特殊情况就是一开始只有一个或者为空
(每轮处理的都是新数字!!!)
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
| import java.util.*;
public class Solution { public ListNode deleteDuplication(ListNode pHead) { if (pHead == null || pHead.next == null) { return pHead; } if (pHead.val != pHead.next.val) { pHead.next = deleteDuplication(pHead.next); return pHead; } else { int val = pHead.val; while (pHead.next != null) { if (pHead.next.val != val) { break; } pHead = pHead.next; } return deleteDuplication(pHead.next); } } }
|
JZ18 删除链表的节点
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
| import java.util.*;
public class Solution {
public ListNode deleteNode (ListNode head, int val) { if (head.val == val) { return head.next; } ListNode last = head, temp = last.next; while (temp != null) { if (temp.val == val) { last.next = temp.next; break; } else { last = temp; temp = temp.next; } } return head; } }
|
树
树的递归
JZ55 二叉树的深度
深度就是左子树和右子树的最大深度+1,为空就返回0,递归秒了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import java.util.*;
public class Solution { public int TreeDepth(TreeNode root) { if (root == null) { return 0; } return Math.max(TreeDepth(root.left), TreeDepth(root.right)) + 1; } }
|
JZ54 二叉搜索树的第k个节点
搜索树直接中序遍历即可
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
| import java.util.*;
public class Solution {
public int KthNode (TreeNode proot, int k) { Stack<TreeNode>stack = new Stack<>(); int count = 0; while (proot != null || !stack.isEmpty()) { while (proot != null) { stack.push(proot); proot = proot.left; } count++; if (count == k) { return stack.pop().val; } proot = stack.pop().right; } return -1; } }
|
JZ7 重建二叉树
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
| import java.util.*;
public class Solution {
public TreeNode reConstructBinaryTree (int[] preOrder, int[] vinOrder) { int preLength = preOrder.length, vinLength = vinOrder.length; if (preLength == 0 || vinLength == 0) { return null; } TreeNode root = new TreeNode(preOrder[0]); for (int i = 0; i < vinLength; i++) { if (preOrder[0] == vinOrder[i]) { root.left = reConstructBinaryTree( Arrays.copyOfRange(preOrder, 1, i + 1), Arrays.copyOfRange(vinOrder, 0, i) ); root.right = reConstructBinaryTree( Arrays.copyOfRange(preOrder, i + 1, preLength), Arrays.copyOfRange(vinOrder, i + 1, vinLength) ); break; } } return root; } }
|
JZ26 树的子结构
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
| import java.util.*;
public class Solution { public boolean HasSubtree(TreeNode root1, TreeNode root2) { if (root1 == null || root2 == null) { return false; } return isSame(root1, root2) || HasSubtree(root1.left, root2) || HasSubtree(root1.right, root2); }
public boolean isSame(TreeNode root1, TreeNode root2) { if (root2 == null) { return true; } if (root1 == null || root1.val != root2.val) { return false; } return isSame(root1.left, root2.left) && isSame(root1.right, root2.right); } }
|
JZ27 二叉树的镜像
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
| import java.util.*;
public class Solution {
public TreeNode Mirror (TreeNode pRoot) { if(pRoot==null){ return null; } TreeNode left = Mirror(pRoot.left); TreeNode right = Mirror(pRoot.right); pRoot.left = right; pRoot.right = left; return pRoot; } }
|
JZ32 从上往下打印二叉树
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
| import java.util.*; import java.util.ArrayList;
public class Solution { public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) { ArrayList<Integer>result = new ArrayList<>(); if (root != null) { Queue<TreeNode>queue = new LinkedList<>(); queue.offer(root); while (!queue.isEmpty()) { TreeNode tmp = queue.poll(); result.add(tmp.val); if (tmp.left != null) { queue.offer(tmp.left); } if (tmp.right != null) { queue.offer(tmp.right); } } } return result; } }
|
JZ33 二叉搜索树的后序遍历序列
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
| import java.util.*; public class Solution { public boolean VerifySquenceOfBST(int [] sequence) { if (sequence == null || sequence.length == 0) { return false; }
return isBSTTree(sequence, 0, sequence.length - 1); }
public boolean isBSTTree(int[] sequence, int l, int r) { if (l >= r) { return true; }
int root = sequence[r];
int k = l; while (sequence[k] < sequence[r]) { k++; } for (int i = k; i < r; i++) { if (sequence[i] < sequence[r]) { return false; } } return isBSTTree(sequence, l, k - 1) && isBSTTree(sequence, k, r - 1); }
}
|
JZ36 二叉搜索树与双向链表
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
| import java.util.*;
public class Solution { TreeNode head = null; TreeNode last = null; public TreeNode Convert(TreeNode pRootOfTree) { if (pRootOfTree != null) { Convert(pRootOfTree.left); if (last == null) { head = pRootOfTree; last = pRootOfTree; } else { last.right = pRootOfTree; pRootOfTree.left = last; last = pRootOfTree; } Convert(pRootOfTree.right); } return head; } }
|
JZ79 判断是不是平衡二叉树
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
| import java.util.*;
public class Solution {
int deep(TreeNode node) { if (node == null) { return 0; } int left = deep(node.left); int right = deep(node.right); return Math.max(left, right) + 1; }
public boolean IsBalanced_Solution (TreeNode pRoot) { if (pRoot == null) { return true; } int left = deep(pRoot.left); int right = deep(pRoot.right); if (Math.abs(left - right) > 1) { return false; } return IsBalanced_Solution(pRoot.left) && IsBalanced_Solution(pRoot.right); } }
|
JZ8 二叉树的下一个结点
仔细观察,可以把中序下一结点归为几种类型:
- 有右子树,下一结点是右子树中的最左结点,例如 B,下一结点是 H
- 无右子树,且结点是该结点父结点的左子树,则下一结点是该结点的父结点,例如 H,下一结点是 E
- 无右子树,且结点是该结点父结点的右子树,则我们一直沿着父结点追朔,直到找到某个结点是其父结点的左子树,如果存在这样的结点,那么这个结点的父结点就是我们要找的下一结点。例如 I,下一结点是 A;例如 G,并没有符合情况的结点,所以 G 没有下一结点
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 class Solution {
public TreeLinkNode GetNext(TreeLinkNode pNode) { if (pNode.right != null) { TreeLinkNode pRight = pNode.right; while (pRight.left != null) { pRight = pRight.left; } return pRight; } if (pNode.next != null && pNode.next.left == pNode) { return pNode.next; } if (pNode.next != null) { TreeLinkNode pNext = pNode.next; while (pNext.next != null && pNext.next.right == pNext) { pNext = pNext.next; } return pNext.next; } return null; } }
|
JZ78 把二叉树打印成多行
这个简单
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
| import java.util.*;
public class Solution {
public ArrayList<ArrayList<Integer>> Print (TreeNode pRoot) { ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>(); if (pRoot != null) { Queue<TreeNode>queue = new LinkedList<>(); queue.offer(pRoot); while (!queue.isEmpty()) { int size = queue.size(); ArrayList<Integer>temp = new ArrayList<>(); for (int i = 0; i < size; i++) { TreeNode node = queue.poll(); temp.add(node.val); if (node.left != null) { queue.offer(node.left); } if (node.right != null) { queue.offer(node.right); } } if (!temp.isEmpty()) { result.add(temp); } } } return result; } }
|
JZ37 序列化二叉树
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 61 62 63 64 65 66 67
| import java.util.*;
public class Solution { String Serialize(TreeNode root) { if (root == null) { return "#"; } StringBuilder stringBuilder = new StringBuilder(); Queue<TreeNode> queue = new LinkedList<>(); queue.offer(root);
while (!queue.isEmpty()) { TreeNode node = queue.poll(); if (node == null) { stringBuilder.append("#,"); } else { stringBuilder.append(node.val).append(","); queue.offer(node.left); queue.offer(node.right); } }
return stringBuilder.toString(); }
TreeNode Deserialize(String str) { if (str.equals("#")) { return null; } String[] nodes = str.split(","); TreeNode root = new TreeNode(Integer.parseInt(nodes[0])); Queue<TreeNode> queue = new LinkedList<>(); queue.offer(root); int index = 1;
while (!queue.isEmpty()) { TreeNode node = queue.poll(); if (!nodes[index].equals("#")) { node.left = new TreeNode(Integer.parseInt(nodes[index])); queue.offer(node.left); } index++;
if (!nodes[index].equals("#")) { node.right = new TreeNode(Integer.parseInt(nodes[index])); queue.offer(node.right); } index++; }
return root; } }
|
JZ84 二叉树中和为某一值的路径(三)
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
| import java.util.*;
public class Solution {
public int FindPath (TreeNode root, int sum) { if (root == null) { return 0; } return FindPathByNode(root, sum) + FindPath(root.left, sum) + FindPath(root.right, sum); }
public int FindPathByNode(TreeNode root, int sum) { if (root == null) { return 0; } if (sum - root.val == 0) { return FindPathByNode(root.left, sum - root.val) + FindPathByNode(root.right, sum - root.val) + 1; } else { return FindPathByNode(root.left, sum - root.val) + FindPathByNode(root.right, sum - root.val); } } }
|
优化
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
| import java.util.*;
public class Solution {
public int FindPath (TreeNode root, int sum) { if (root == null) { return 0; } return FindPathByNode(root, sum) + FindPath(root.left, sum) + FindPath(root.right, sum); }
public int FindPathByNode(TreeNode root, int sum) { if (root == null) { return 0; } int result = 0; if (root.val == sum) { result++; }
result += FindPathByNode(root.left, sum - root.val); result += FindPathByNode(root.right, sum - root.val); return result; } }
|
JZ86 在二叉树中找到两个节点的最近公共祖先
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
| import java.util.*;
public class Solution {
public int lowestCommonAncestor(TreeNode root, int o1, int o2) { return helper(root, o1, o2).val; }
public TreeNode helper(TreeNode root, int o1, int o2) { if (root == null || root.val == o1 || root.val == o2) return root; TreeNode left = helper(root.left, o1, o2); TreeNode right = helper(root.right, o1, o2); if (left == null) return right; if (right == null) return left; return root; } }
|
队列 & 栈
JZ31 栈的压入、弹出序列
下面有原地栈的方法
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
| import java.util.*;
public class Solution {
public boolean IsPopOrder (int[] pushV, int[] popV) { Stack<Integer>stack = new Stack<>(); int j = 0; for (int i = 0; i < pushV.length; i++) { while (j < pushV.length && (stack.isEmpty() || stack.peek() != popV[i])) { stack.push(pushV[j++]); } if (stack.pop() != popV[i]) { return false; } } return true; } }
|
也可以一直入栈,每次遍历出栈的是否相等,相等就继续出栈,直到栈空了,最后看栈是不是空放回即可,借鉴了原地栈的思路
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
| import java.util.*;
public class Solution {
public boolean IsPopOrder (int[] pushV, int[] popV) { int n = 0; int i = 0; for(int temp:pushV){ pushV[n] = temp;
while(n>=0&&pushV[n]==popV[i]){ n--; i++; }
n++; } return n==0; } }
|
JZ73 翻转单词序列
看下面双指针
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
| import java.util.*;
public class Solution { public String ReverseSentence(String str) { Stack<Character> stack1 = new Stack<>(); ArrayList<String> list1 = new ArrayList<>(); StringBuilder result = new StringBuilder();
if (str == null || str.length() == 0) { return ""; }
for (int i = str.length() - 1; i >= 0; i--) { char c = str.charAt(i);
if (c == ' ') { if (!stack1.isEmpty()) { StringBuilder temp = new StringBuilder(); while (!stack1.isEmpty()) { temp.append(stack1.pop()); } list1.add(temp.toString()); } list1.add(" "); } else { stack1.push(c); } }
if (!stack1.isEmpty()) { StringBuilder temp = new StringBuilder(); while (!stack1.isEmpty()) { temp.append(stack1.pop()); } list1.add(temp.toString()); }
for (String word : list1) { result.append(word); }
return result.toString(); } }
|
双指针
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
| import java.util.*; public class Solution { public String ReverseSentence(String str) { String temp = str.trim();
int j = str.length() - 1, i = j; StringBuilder result = new StringBuilder(); while (i >= 0) { while (i >= 0 && temp.charAt(i) != ' ') { i--; } result.append(temp.substring(i + 1, j + 1) + " "); while (i >= 0 && temp.charAt(i) == ' ') { i--; } j = i; } return result.toString().trim(); } }
|
JZ59 滑动窗口的最大值
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
| import java.util.*;
public class Solution {
public ArrayList<Integer> maxInWindows (int[] num, int size) { ArrayList<Integer>result = new ArrayList<>(); if (size > num.length || size == 0) { return result; } int index = 0; PriorityQueue<Integer> queue = new PriorityQueue<>((o1, o2)->o2 - o1); for (int i = 0; i < size - 1; i++) { queue.offer(num[i]); } for (int i = size - 1; i < num.length; i++) { queue.offer(num[i]); result.add(queue.peek()); queue.remove(num[index++]); } return result; } }
|
搜索算法
一次过,爽,其实就是二分搜索,出现三种情况,相等就递归搜索左边和右边的加1,小于就往右边递归,大于就往左边递归,左指针大于右指针就说明没有找到
JZ53 数字在升序数组中出现的次数
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
| import java.util.*;
public class Solution { public int dichotomy(int[] nums,int k,int left,int right){ if(left>right){ return 0; } int mid = (left+right)/2; if(nums[mid]>k){ return dichotomy(nums,k,left,mid-1); }else if(nums[mid]<k){ return dichotomy(nums,k,mid+1,right); }else { return dichotomy(nums,k,left,mid-1)+dichotomy(nums,k,mid+1,right)+1; } }
public int GetNumberOfK (int[] nums, int k) { return dichotomy(nums,k,0,nums.length-1); } }
|
JZ11 旋转数组的最小数字
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
| import java.util.*;
public class Solution {
public int minNumberInRotateArray (int[] nums) { int left = 0, right = nums.length - 1; while (left <= right) { int mid = left + (right - left) / 2; if (nums[mid] > nums[right]) { left = mid + 1; } else if (nums[mid] > nums[right]) { right = mid; } else { right--; } } return nums[left]; } }
|
JZ38 字符串的排列
严格算不重复的全排列,那么就先排序,字典输出,如果前一个字符没有使用过并且跟他相等说明当前字符也不能用来全排列(前面用过但跟他相等),剪枝,全排列就回溯法
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
| import java.util.*;
public class Solution {
public void backTrack(ArrayList<String>result, String str, StringBuilder temp, boolean[] isDistance, int index) { if (index == str.length()) { result.add(new String(temp)); return; } for (int i = 0; i < str.length(); i++) { if (isDistance[i]) { continue; } if (i > 0 && str.charAt(i - 1) == str.charAt(i) && !isDistance[i - 1]) { continue; } temp.append(str.charAt(i)); isDistance[i] = true; backTrack(result, str, temp, isDistance, index + 1); temp.deleteCharAt(temp.length() - 1); isDistance[i] = false; } }
public ArrayList<String> Permutation (String str) { ArrayList<String> result = new ArrayList<String>(); char[] chars = str.toCharArray(); Arrays.sort(chars); backTrack(result, new String(chars), new StringBuilder(), new boolean[str.length()], 0); return result; } }
|
JZ44 数字序列中某一位的数字
找规律,感觉好难
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
| import java.util.*;
public class Solution {
public int findNthDigit (int n) { int digit = 1; long start = 1; long count = 9; while (n > count) { n -= count; digit++; start *= 10; count = digit * start * 9; } long num = start + (n - 1) / digit; return Long.toString(num).charAt((n - 1) % digit) - '0'; } }
|
动态规划
动态规划算法的基本思想是:将待求解的问题分解成若干个相互联系的子问题,先求解子问题,然后从这些子问题的解得到原问题的解;对于重复出现的子问题,只在第一次遇到的时候对它进行求解,并把答案保存起来,让以后再次遇到时直接引用答案,不必重新求解。动态规划算法将问题的解决方案视为一系列决策的结果。
这里经典的就是以斐波那契数列开始,然后就是0-1背包问题,还有就是要么加上当前数值,要么以当前的数值干什么,更新状态(简单抉择)
贪心算法和动态规划是两种不同的算法设计思想,它们适用于解决不同类型的问题,且并不相互包含。
贪心算法
贪心算法(Greedy Algorithm)是一种在每一步选择中都选择当前最优解的算法思想。它的核心在于选择的局部最优解会带来全局最优解。贪心算法通常不回溯,一旦作出选择,就不再考虑其他可能性。这使得贪心算法在某些问题中非常高效,但也可能因为局部最优选择而无法得到全局最优解。
贪心算法通常用于问题具有 贪心选择性质(即每一步选择的局部最优解不会影响全局最优解)和 最优子结构性质(问题的最优解包含其子问题的最优解)的问题。
动态规划
动态规划(Dynamic Programming, DP)是一种通过将问题分解为多个子问题来解决问题的算法思想。它通常适用于有重叠子问题和最优子结构的问题。动态规划通过存储中间结果来避免重复计算,提升算法效率。
动态规划的核心思想包括 状态定义、状态转移方程 和 初始状态。它一般分为两种方式:自顶向下(带备忘录的递归)和 自底向上(迭代)。
这里以斐波那契数列给出两种区别
1 2 3 4 5 6 7 8 9 10 11 12 13
| fun fib(n: Int, memo: IntArray): Int { if (n <= 1) return n if (memo[n] != -1) return memo[n] memo[n] = fib(n - 1, memo) + fib(n - 2, memo) return memo[n] }
fun main() { val n = 10 val memo = IntArray(n + 1) { -1 } println(fib(n, memo)) }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| fun fib(n: Int): Int { if (n <= 1) return n val dp = IntArray(n + 1) dp[0] = 0 dp[1] = 1 for (i in 2..n) { dp[i] = dp[i - 1] + dp[i - 2] } return dp[n] }
fun main() { val n = 10 println(fib(n)) }
|
总结
- 贪心算法 是每一步都选择当前的最优解,但不保证全局最优。
- 动态规划 则通过记录所有可能的情况,确保最终解是全局最优。
JZ85 连续子数组的最大和(二)
思路:
既然是连续子数组,如果我们拿到了当前的和,对于后面一个即将加入的元素,如果加上他这一串会变得更大,我们肯定会加上它,如果它自己会比加上前面这一串更大,说明从它自己开始连续子数组的和可能会更大。
那我们可以用dp数组表示以下标i为终点的最大连续子数组和,则每次遇到一个新的数组元素,连续的子数组要么加上变得更大,要么它本身就更大,因此状态转移为dp[i]=max(dp[i−1]+array[i],array[i]),这是最基本的求连续子数组的最大和。
但是题目要求需要返回长度最长的一个,我们则每次用left、right记录该子数组的起始,需要更新最大值的时候(要么子数组和更大,要么子数组和相等的情况下区间要更长)顺便更新最终的区间首尾,这样我们的区间长度就是最长的。
具体做法:
- step 1:创建动态规划辅助数组,记录到下标i为止的最大连续子数组和,下标为0的时候,肯定等于原数组下标为0的元素。
- step 2:准备左右区间双指针记录每次连续子数组的首尾,再准备两个双指针记录最大和且区间最长的连续子数组的首尾。
- step 3:遍历数组,对于每个元素用上述状态转移公式记录其dp值,更新区间首尾(如果需要)。
- step 4:出现一个最大值。且区间长度更大的时候,更新记录最长区间的双指针。
- step 5:根据记录的最长子数组的位置取数组。
基础版本
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
| import java.util.*; public class Solution { public int[] FindGreatestSumOfSubArray (int[] array) { int[] dp = new int[array.length]; dp[0] = array[0]; int maxsum = dp[0]; int left = 0, right = 0; int resl = 0, resr = 0; for(int i = 1; i < array.length; i++){ right++; dp[i] = Math.max(dp[i - 1] + array[i], array[i]); if(dp[i - 1] + array[i] < array[i]) left = right; if(dp[i] > maxsum || dp[i] == maxsum && (right - left + 1) > (resr - resl + 1)){ maxsum = dp[i]; resl = left; resr = right; } } int[] res = new int[resr - resl + 1]; for(int i = resl; i <= resr; i++) res[i - resl] = array[i]; return res; } }
|
进阶,只保存dp[i-1]即可,不用每次都保存dp
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
| import java.util.*;
public class Solution {
public int[] FindGreatestSumOfSubArray (int[] array) { int max = array[0], maxl = 0, maxr = 0; int last = array[0], l = 0, r = 0, temp = 0; for (int i = 1; i < array.length; i++) { r++; temp = Math.max(array[i], last + array[i]); if (array[i] > last + array[i]) { l = r; } if (temp > max || temp == max && (r - l + 1) > (maxr - maxl + 1)) { maxl = l; maxr = r; max = temp; } last = temp; } int[] result = new int[maxr - maxl + 1]; for (int i = maxl; i <= maxr; i++) { result[i - maxl] = array[i]; } return result; } }
|
JZ69 跳台阶
在本子上列出来就找到规律了,首先1只能走1步到;2能走1+1,2;3能1+1+1,2+1,1+2;
4能1+1+1+1,2+1+1,1+2,1+1+2,2+2,到这里就发现了,4的时候就是3的情况上走1步和2的情况上走2步的情况可能总和,3的时候一样,1走两步和2走1步的情况总和,就是斐波那契数列的规律,2的时候变化了一下数值而已
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import java.util.*;
public class Solution {
public int jumpFloor (int number) { int first = 1, second = 1; for (int i = 2; i <= number; i++) { int temp = first + second; first = second; second = temp; } return second; } }
|
JZ10 斐波那契数列
first为前面第一个值,second为第二个值,每次计算first+second放到second,first变为之前的second即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import java.util.*;
public class Solution {
public int Fibonacci (int n) { if (n == 1 || n == 2) { return 1; } int first = 1, second = 1; for (int i = 2; i < n; i++) { int temp = first + second; first = second; second = temp; } return second; } }
|
JZ71 跳台阶扩展问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import java.util.*;
public class Solution {
public int jumpFloorII (int number) { int lastSum = 0, result = 0; for (int i = 0; i < number; i++) { result = lastSum + 1; lastSum += result; } return result; } }
|
JZ70 矩形覆盖
递归是一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。因此递归过程,最重要的就是查看能不能讲原本的问题分解为更小的子问题,这是使用递归的关键。
首先如果n=0,则只有0种;
如果n=1,也只有1种;
如果n=2,有横竖2种情况;
如果n=3,有3种情况;
而如果n=4,有5种情况;
由规律发现,2*n矩阵的情况数为f(n)=f(n−1)+f(n−2),即这就是一个斐波那契数列,按照斐波那契数列的解法来即可,需要注意不同点在于n小于等于2时,都只有n种。
1 2 3 4 5 6 7 8 9 10
| import java.util.*; public class Solution { public int rectCover(int target) { if (target <= 2) { return target; } return rectCover(target - 1) + rectCover(target - 2); } }
|
迭代
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import java.util.*; public class Solution { public int rectCover(int target) { if (target <= 2) { return target; } int first = 1, second = 2, temp = 0; for (int i = 2; i < target; i++) { temp = first + second; first = second; second = temp; } return temp; } }
|
JZ47 礼物的最大价值
第一行和第一列只有一种走法,剩下位置就是要么从上面来,要么从左边来,判断哪种走法大就用哪种走法,保存到dp数组即可
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
| import java.util.*;
public class Solution {
public int maxValue (int[][] grid) { int[][] dp = new int[grid.length][grid[0].length]; dp[0][0] = grid[0][0]; for (int j = 1; j < grid[0].length; j++) { dp[0][j] = dp[0][j - 1] + grid[0][j]; } for (int i = 1; i < grid.length; i++) { dp[i][0] = dp[i - 1][0] + grid[i][0]; } for (int i = 1; i < grid.length; i++) { for (int j = 1; j < grid[0].length; j++) { dp[i][j] = Math.max( grid[i][j] + dp[i][j - 1], grid[i][j] + dp[i - 1][j]); } } return dp[grid.length - 1][grid[0].length - 1]; } }
|
其实还有优化空间,只需要一维数组即可,一开始处理最上面一行,只保存一行的值,之后从第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 32 33 34 35
| import java.util.*;
public class Solution {
public int maxValue (int[][] grid) { int[] dp = new int[grid[0].length]; dp[0] = grid[0][0]; for (int j = 1; j < grid[0].length; j++) { dp[j] = dp[j - 1] + grid[0][j]; } for (int i = 1; i < grid.length; i++) { for (int j = 0; j < grid[0].length; j++) { if (j == 0) { dp[j] += grid[i][j]; } else { dp[j] = Math.max(dp[j - 1] + grid[i][j], dp[j] + grid[i][j]); } } } return dp[grid[0].length - 1] ; } }
|
JZ48 最长不含重复字符的子字符串
如果对于某个前面的子串,如果我们新加入一个字符,与前面的都不重复,那么最长无重复子串肯定就是在前面的基础上加1,如果与前面重复了,那就是当前位置减去它重复之前字符出现的位置的长度。因此我们使用动态规划递推。
具体做法:
- step 1:dp[i]表示以下标i结尾的字符串最长不含重复子串的长度,用哈希表记录是否重复出现字符,并记录其位置。
- step 2:遍历字符串,哈希表中没有出现过的就不是重复,因此考虑dp[i]=dp[i−1]+1,即在前一个字符的基础上加上它。
- step 3:哈希表中出现过的,这是重复的字符,考虑i−mp[s[i−1]],但是为了防止中间出现其他重复的字符,还是应该考虑它的前一个字符的基础,因此实际转移方程为dp[i]=min(dp[i−1]+1,i−mp[s[i−1]])。
- step 4:遍历过程中遇到的字符都要加入哈希表,同时维护最大的长度。
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
| import java.util.*;
public class Solution {
public int lengthOfLongestSubstring (String s) { int[] dp = new int[s.length()]; int result = 0; HashMap<Character, Integer>map = new HashMap<>(); for (int i = 0; i < s.length(); i++) { if (!map.containsKey(s.charAt(i))) { if (i == 0) { dp[0] = 1; } else { dp[i] = dp[i - 1] + 1; } } else { dp[i] = Math.min(dp[i - 1] + 1, i - map.get(s.charAt(i))); } map.put(s.charAt(i), i); result = Math.max(result, dp[i]); } return result; } }
|
JZ46 把数字翻译成字符串
难搞
对于普通数组1-9,译码方式只有一种,但是对于11-19,21-26,译码方式有可选择的两种方案,因此我们使用动态规划将两种方案累计。
具体做法:
- step 1:用辅助数组dp表示前i个数的译码方法有多少种。
- step 2:对于一个数,我们可以直接译码它,也可以将其与前面的1或者2组合起来译码:如果直接译码,则dp[i]=dp[i−1];如果组合译码,则dp[i]=dp[i−2]。
- step 3:对于只有一种译码方式的,选上种dp[i−1]即可,对于满足两种译码方式(10,20不能)则是dp[i−1]+dp[i−2]
- step 4:依次相加,最后的dp[length]即为所求答案。
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
| import java.util.*;
public class Solution {
public int solve (String nums) { if (nums.equals("0")) { return 0; } if (nums.equals("10") || nums.equals("20")) { return 1; } for (int i = 1; i < nums.length(); i++) { if (nums.charAt(i) == '0') { if (nums.charAt(i - 1) != '1' && nums.charAt(i - 1) != '2') { return 0; } } } int[] dp = new int[nums.length() + 1]; Arrays.fill(dp, 1); for (int i = 2; i <= nums.length(); i++) { if ((nums.charAt(i - 2) == '1' && nums.charAt(i - 1) != '0') || (nums.charAt(i - 2) == '2' && nums.charAt(i - 1) > '0' && nums.charAt(i - 1) < '7')) { dp[i] = dp[i-1]+dp[i-2]; }else{ dp[i] = dp[i-1]; } } return dp[nums.length()]; } }
|
回溯
JZ12 矩阵中的路径
每个位置作为起始点开始遍历回溯,对于每轮,传入矩阵,字符串,当前下标和当前判断字符串的位置,如果位置越界或者当前位置不等就结束(剪枝),然后当当前位置等于字符串长度就可以返回了,一开始判断了是否相等了,之后就记录当前位置的元素,后面把他赋值为一个不可能出现的字符,后面分别向左向右向上向下走递归看结果,最后把值还原并返回上下左右的结果。(这里每个位置都可以作为出发点)
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
| import java.util.*;
public class Solution {
public boolean hasPath (char[][] matrix, String word) { for(int i = 0;i<matrix.length;i++){ for(int j = 0;j<matrix[0].length;j++){ if(backtrack(matrix,word,i,j,0)){ return true; } } } return false; }
public boolean backtrack(char[][] matrix,String word,int i,int j,int index){
if(i>=matrix.length||i<0||j>=matrix[0].length||j<0||matrix[i][j]!=word.charAt(index)){ return false; }
if(index==word.length()-1){ return true; }
char temp = matrix[i][j]; matrix[i][j] = '.'; boolean res = backtrack(matrix,word,i-1,j,index+1)||backtrack(matrix,word,i+1,j,index+1)||backtrack(matrix,word,i,j+1,index+1)||backtrack(matrix,word,i,j-1,index+1); matrix[i][j] = temp; return res; } }
|
JZ13 机器人的运动范围
回溯思路跟上面很像,就是先越界判断剪纸条,上面用了技巧,不用保存是否访问过,这里需要,同样传入位置,看当前位置是否满足条件,不满足返回,满足结果加1修改访问数组继续向4个方向移动,最后返回全局变量的结果就行。(需要注意的是这里访问过不能再访问,别把flags数组又修改回去)
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
| import java.util.*; public class Solution {
int result = 0;
public int movingCount(int threshold, int rows, int cols) { boolean[][] flags = new boolean[rows][cols]; backtrack(threshold,rows,cols,0,0,flags); return result; }
public void backtrack(int threshold, int rows, int cols , int i, int j, boolean[][] flags) { if (i < 0 || i >= rows || j < 0 || j >= cols || flags[i][j]) { return; } if(getSum(i)+getSum(j)>threshold){ return; } result++; flags[i][j] = true; backtrack(threshold,rows,cols,i+1,j,flags); backtrack(threshold,rows,cols,i-1,j,flags); backtrack(threshold,rows,cols,i,j+1,flags); backtrack(threshold,rows,cols,i,j-1,flags); }
public int getSum(int num) { int result = 0; while (num != 0) { result += num % 10; num /= 10; } return result; } }
|
排序
JZ3 数组中重复的数字
由于题目说了,数字永远小于给定数组长度,那么就可以借助下标来完成,相同下标存在数字,那么就说明存在一样的了,可以新开一个数组搞定,题目要求on空间即可,这里学空间o1的方法
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
| import java.util.*;
public class Solution {
public int duplicate (int[] numbers) { int result = -1; for (int i = 0; i < numbers.length; i++) { if (numbers[i] != i) { if (numbers[numbers[i]] == numbers[i]) { result = numbers[i]; } else { int temp = numbers[numbers[i]]; numbers[numbers[i]] = numbers[i]; numbers[i] = temp; i--; } } } return result; } }
|
JZ51 数组中的逆序对
经典逆序对问题,二分查找
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
| import java.util.*;
public class Solution { public int mod = 1000000007; public int mergeSort(int[] nums, int[] temp, int start, int end) { if (start >= end) { return 0; } int mid = (start + end) / 2; int res = mergeSort(nums, temp, start, mid) + mergeSort(nums, temp, mid + 1, end); res %= mod; int left = start, right = mid + 1, index = start; while (left <= mid && right <= end) { if (nums[left] > nums[right]) { temp[index++] = nums[right++]; res += (mid - left + 1); res %= mod; } else { temp[index++] = nums[left++]; } } while (left <= mid) { temp[index++] = nums[left++]; } while (right <= end) { temp[index++] = nums[right++]; } for (int i = start; i < index; i++) { nums[i] = temp[i]; } return res % mod; }
public int InversePairs (int[] nums) { int[] temp = new int[nums.length]; return mergeSort(nums, temp, 0, nums.length - 1); } }
|
JZ40 最小的K个数
top k问题,直接优先队列搞定,注意特殊测试样例即可,k就是优先队列的数量,保存最小的k个即可,一开始就填充k个,后面不断与队头比较,比他小就加入并抛出对头,那么要保证队列内队头最大,那么就是大顶堆
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
| import java.util.*;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution (int[] input, int k) { ArrayList<Integer> result = new ArrayList<Integer>(); if (k != 0 && input.length != 0) { PriorityQueue<Integer>queue = new PriorityQueue<>((o1, o2)->o2 - o1); for (int i = 0; i < k; i++) { queue.offer(input[i]); } for (int i = k; i < input.length; i++) { if (queue.peek() > input[i]) { queue.poll(); queue.offer(input[i]); } }
for (int i = 0; i < k; i++) { result.add(queue.poll()); } }
return result; } }
|
JZ41 数据流中的中位数
类似top k问题,这里是找中位数,奇数就取中间的,偶数取中间和的平均数,top k都是用优先队列解决,这里可以思考到使用两个优先队列,但是优先队列只能取头部的值,那么就要考虑两个头部值是连在一起的,并且两边数量一样多,那么就是一个大顶堆,一个小根堆,这时候为奇数怎么办,那么就要考虑有一个是多的,可以考虑统一哪个比较多,这里让大顶堆统一多。那么最后大顶堆是放偏小的数,小顶堆放偏大的数,因为我们要考虑中间的部分,而优先队列只能取头部,要让头部在中间部分就要这么存。
总体插入思路就是先加入大顶堆,每次只插入一个,那么取出头部即可,放入小顶堆,之后平衡数量,让大顶堆多一个或者平等,最后根据条件判断即可。
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
| import java.util.*;
public class Solution {
PriorityQueue<Integer>big = new PriorityQueue<Integer>( (o1, o2)->o2 - o1 );
PriorityQueue<Integer>small = new PriorityQueue<Integer>();
public void Insert(Integer num) { big.offer(num); small.offer(big.poll()); if (big.size() < small.size()) { big.offer(small.poll()); } }
public Double GetMedian() { if (big.size() > small.size()) { return (double)big.peek(); } else { return (double)(small.peek() + big.peek()) / 2; } }
}
|
位运算
用位运算维护状态码,同事直呼牛X!位运算是一种非常高效的运算方式。在算法考察中比较常见,它使用位级别的操作来表示和控制状 - 掘金 (juejin.cn)
- 与(AND)运算:只有当两个位都是1时,结果才是1(
a & b
)。
- 或(OR)运算:如果两个位中至少有一个为1,那么结果就是1(
a | b
)。
- 异或(XOR)运算:如果两个位不同,则结果为1(
a ^ b
)。
- 非(NOT)运算:反转位的值(
~a
)。
- 左移:将位向左移动,右侧填充0(
a << b
)。
- 右移:将位向右移动,左侧填充0(
a >> b
)。
算法题每日一练—第45天:位运算在计算机内部,各种信息都必须经过数字化编码后才能被传送、存储和处理,所有的数据以二进 - 掘金 (juejin.cn)
JZ65 不用加减乘除做加法
下面博客分析了加法和减法,负数就是取反+1,+1就用加法表示就好
算法 | 如何用位运算实现加减运算?开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第35天,本文主 - 掘金 (juejin.cn)
1 2 3 4 5 6 7 8 9 10 11 12
| import java.util.*; public class Solution { public int Add(int num1, int num2) { while (num2 != 0) { int temp = num1 ^ num2; num2 = (num1 & num2) << 1; num1 = temp; } return num1; } }
|
JZ15 二进制中1的个数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import java.util.*;
public class Solution {
public int NumberOf1 (int n) { int result = 0; for (int i = 0; i < 32; i++) { if ((n & (1 << i)) != 0) { result++; } } return result; } }
|
JZ16 数值的整数次方
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import java.util.*; public class Solution { public double Power(double base, int exponent) { if (exponent < 0) { base = 1 / base; exponent = -exponent; }
double result = 1.0; for (int i = 0; i < exponent; i++) { result *= base; } return result; } }
|
位运算减少时间复杂度
这里就是运用了二分法的思想,如果计算5的10次方,一直累乘就需要计算9次,如果5∗5=25(二次)、25∗25=62525∗25=625(四次)、625∗625=…625∗625=…(八次),这样时间缩短到了log2 n,
计算x的13次方的话,13 = 1 * 2^3 + 1 * 2^2 + 0 * 2^1 + 1 * 2^0,最后可以为x^13 = x^(2^3) * x^(2^2) * x^(2^0),也就是当某位为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
| import java.util.*; public class Solution { public double Power(double base, int exponent) { if (exponent < 0) { base = 1 / base; exponent = -exponent; } return solve(base,exponent); }
public static double solve(double x,int y){ double result = 1.0;
while(y!=0){
if((y & 1)!=0){ result*=x; }
x *= x;
y = y >> 1; }
return result; } }
|
JZ56 数组中只出现一次的两个数字
- 利用异或特性:异或运算 (
^
) 有一个很好的性质:相同的数字异或后结果为0,不同的数字异或后得到的结果与这两个数之间的位差有关。因此,如果数组中只有两个数出现一次,其他数都出现两次,遍历整个数组对所有数做异或操作,结果就是这两个只出现一次的数的异或结果。
- 区分这两个数:得到的异或结果可以帮助我们找到这两个数在某一位上的不同,这可以用来区分这两个数。
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
| import java.util.*;
public class Solution {
public int[] FindNumsAppearOnce (int[] nums) {
int temp = 0; for (int i = 0; i < nums.length; i++) { temp ^= nums[i]; }
int k = 1; while ((k & temp) == 0) { k <<= 1; }
int res1 = 0, res2 = 0; for (int i = 0; i < nums.length; i++) { if ((nums[i] & k) != 0) { res1 ^= nums[i]; } else { res2 ^= nums[i]; } }
if (res1 < res2) { return new int[] {res1, res2}; } else { return new int[] {res2, res1}; } } }
|
JZ64 求1+2+3+…+n
1 2 3 4 5 6 7 8 9
| import java.util.*; public class Solution { public int Sum_Solution(int n) { boolean flag = (n > 1) && ((n += Sum_Solution(n - 1)) > 0); return n; } }
|
模拟
JZ29 顺时针打印矩阵
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
| import java.util.ArrayList;
public class Solution { public ArrayList<Integer> printMatrix(int [][] matrix) { ArrayList<Integer> result = new ArrayList<>(); int rows = matrix.length; if (rows == 0) { return result; } int cols = matrix[0].length; if (cols == 0) { return result; }
int top = 0, bottom = rows - 1, left = 0, right = cols - 1;
while (top <= bottom && left <= right) { for (int i = left; i <= right; i++) { result.add(matrix[top][i]); } top++;
for (int i = top; i <= bottom; i++) { result.add(matrix[i][right]); } right--;
if (top <= bottom) { for (int i = right; i >= left; i--) { result.add(matrix[bottom][i]); } bottom--; }
if (left <= right) { for (int i = bottom; i >= top; i--) { result.add(matrix[i][left]); } left++; } }
return result; } }
|
JZ61 扑克牌顺子
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
| import java.util.*;
public class Solution {
public boolean IsContinuous (int[] numbers) { Arrays.sort(numbers); int zero = 0, temp = 0; for (int i = 0; i < numbers.length - 1; i++) { if (numbers[i] == 0) { zero++; } else { if (numbers[i + 1] - numbers[i] == 0) { return false; } else { temp += numbers[i + 1] - numbers[i] - 1; } } } if (zero >= temp) { return true; } return false; } }
|
其他算法
JZ49 丑数
也可以用优先队列,看乘2乘3乘5哪个未出现过就加进去优先队列,每轮取优先队列最小的一个(取index次就是正确值了),用哈希表去重
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
| import java.util.*;
public class Solution { private int findMin(int x,int y,int z){ int temp = Math.min(x,y); return Math.min(temp,z); }
public int GetUglyNumber_Solution (int index) { if(index==0){ return 0; } ArrayList<Integer>result = new ArrayList<>(); result.add(1); int count = 1; int i = 0,j = 0,k = 0; while(count<index){ result.add(findMin(result.get(i)*2,result.get(j)*3,result.get(k)*5)); count++; if(result.get(count-1)==result.get(i)*2){ i++; } if(result.get(count-1)==result.get(j)*3){ j++; } if(result.get(count-1)==result.get(k)*5){ k++; } } return result.get(index-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
| import java.util.*; public class Solution { public int GetUglyNumber_Solution(int index) { if(index == 0) return 0; int[] factors = {2, 3, 5}; HashMap<Long, Integer> mp = new HashMap<>(); PriorityQueue<Long> pq = new PriorityQueue<>(); mp.put(1L, 1); pq.offer(1L); long res = 0; for(int i = 0; i < index; i++){ res = pq.poll(); for(int j = 0; j < 3; j++){ long next = (long)res * factors[j]; if(!mp.containsKey(next)){ mp.put(next, 1); pq.offer(next); } } } return (int)res; } }
|
JZ74 和为S的连续正数序列
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
| import java.util.*;
public class Solution {
public ArrayList<ArrayList<Integer>> FindContinuousSequence (int sum) { ArrayList<ArrayList<Integer>>result = new ArrayList<ArrayList<Integer>>(); if(sum==0||sum==1){ return result; } ArrayList<Integer>temp = new ArrayList<>(); int total = 0; int strat = 1; while(total<=sum){ temp.add(strat); total+=strat; strat++; } if(total>sum&&!temp.isEmpty()){ strat--; temp.remove(temp.size()-1); total-=strat; } for(int i = 0;i<=sum/2;i++){ if(total==sum){ ArrayList<Integer>possible = new ArrayList<>(); possible.addAll(temp); result.add(possible); } temp.add(strat); total+=strat; if(total>sum){ total-=temp.get(0); temp.remove(0); if(total>sum){ strat--; total-=temp.get(temp.size()-1); temp.remove(temp.size()-1); } } strat++; } return result; } }
|
JZ57 和为S的两个数字
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import java.util.*; import java.util.ArrayList; public class Solution { public ArrayList<Integer> FindNumbersWithSum(int [] array, int sum) { ArrayList<Integer>result = new ArrayList<>(); if (array.length < 2) { return result; } int left = 0, right = array.length - 1; while (left < right) { if (array[left] + array[right] > sum) { right--; } else if (array[left] + array[right] < sum) { left++; } else { result.add(array[left]); result.add(array[right]); return result; } } return result; } }
|
JZ58 左旋转字符串
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
| import java.util.*;
public class Solution {
public String LeftRotateString (String str, int n) { if (str.isEmpty()) { return ""; } int real = n % str.length(); char[] result = new char[str.length()]; int index = 0; for (int i = real; i < str.length(); i++) { result[index++] = str.charAt(i); } for (int i = 0; i < real; i++) { result[index++] = str.charAt(i); } return new String(result); } }
|
JZ62 孩子们的游戏(圆圈中最后剩下的数)
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
| import java.util.*;
public class Solution {
public int LastRemaining_Solution (int n, int m) { if (n == 0) { return -1; }
ArrayList<Integer> list = new ArrayList<>(); for (int i = 0; i < n; i++) { list.add(i); }
int index = 0; while (list.size() > 1) { index = (index + m - 1) % list.size(); list.remove(index); }
return list.get(0); } }
|
JZ75 字符流中第一个不重复的字符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import java.util.*; public class Solution { private StringBuilder s = new StringBuilder(); private HashMap<Character, Integer>hashMap = new HashMap<>(); public void Insert(char ch) { s.append(ch); hashMap.put(ch, hashMap.getOrDefault(ch, 0) + 1); } public char FirstAppearingOnce() { for (int i = 0; i < s.length(); i++) { if (hashMap.get(s.charAt(i)) == 1) { return s.charAt(i); } } return '#'; } }
|
JZ14 剪绳子
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
| import java.util.*;
public class Solution {
public int cutRope (int n) {
if (n == 2 || n == 3) { return n - 1; } int result = 1; while (n > 4) { n -= 3; result *= 3; } return n * 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 26 27 28 29 30
| import java.util.*;
public class Solution {
public int cutRope (int n) {
if (n == 2 || n == 3) { return n - 1; } else if (n % 3 == 0) { return (int)Math.pow(3, n / 3); } else if (n % 3 == 1) { return 4 * (int)Math.pow(3, (n - 4) / 3); } else { return 2 * (int)Math.pow(3, n / 3); } } }
|