簡述
極客時間算法40講中所出現的leetcode算法題
題目
【鏈表】reverse-linked-list(反轉一個單鏈表)
示例:
輸入: 1->2->3->4->5->NULL
輸出: 5->4->3->2->1->NULL
代碼
遞歸
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
if(head == null || head.next == null) return head;
ListNode node = reverseList(head.next);
head.next.next = head;
head.next = null;
return node;
}
}
迭代
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
ListNode prev = null;
while(head != null){
ListNode tmp = head.next;
head.next = prev;
prev = head;
head = tmp;
}
return prev;
}
}
【鏈表】swap-nodes-in-pairs(兩兩交換鏈表中的節點)
給定一個鏈表,兩兩交換其中相鄰的節點,並返回交換後的鏈表。
你不能只是單純的改變節點內部的值,而是需要實際的進行節點交換。
示例:
給定 1->2->3->4, 你應該返回 2->1->4->3.
代碼
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode swapPairs(ListNode head) {
if(head == null || head.next == null) return head;
ListNode res = head.next;
ListNode pre = new ListNode(-1);
pre.next = head;
while(pre.next != null && pre.next.next != null){
ListNode a = pre.next;
ListNode b = a.next;
pre.next = b;
a.next = b.next;
b.next = a;
pre = a;
}
return res;
}
}
【鏈表】linked-list-cycle(環形鏈表)
給定一個鏈表,判斷鏈表中是否有環。
爲了表示給定鏈表中的環,我們使用整數 pos
來表示鏈表尾連接到鏈表中的位置(索引從 0 開始)。 如果 pos
是 -1
,則在該鏈表中沒有環。
示例 1:
輸入:head = [3,2,0,-4], pos = 1
輸出:true
解釋:鏈表中有一個環,其尾部連接到第二個節點。
示例 2:
輸入:head = [1,2], pos = 0
輸出:true
解釋:鏈表中有一個環,其尾部連接到第一個節點。
示例 3:
輸入:head = [1], pos = -1
輸出:false
解釋:鏈表中沒有環。
代碼
雙指針
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public boolean hasCycle(ListNode head) {
if(head == null || head.next == null || head.next.next == null) return false;
ListNode slow = head;
ListNode fast = head;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow){
return true;
}
}
return false;
}
}
set檢測重複
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public boolean hasCycle(ListNode head) {
Set<ListNode> set = new HashSet<>();
while(head != null){
if(set.contains(head)) return true;
set.add(head);
head = head.next;
}
return false;
}
}
【鏈表】linked-list-cycle-ii(環形鏈表 II)
給定一個鏈表,返回鏈表開始入環的第一個節點。 如果鏈表無環,則返回 null
。
爲了表示給定鏈表中的環,我們使用整數 pos
來表示鏈表尾連接到鏈表中的位置(索引從 0 開始)。 如果 pos
是 -1
,則在該鏈表中沒有環。
說明:不允許修改給定的鏈表。
示例 1:
輸入:head = [3,2,0,-4], pos = 1
輸出:tail connects to node index 1
解釋:鏈表中有一個環,其尾部連接到第二個節點。
示例 2:
輸入:head = [1,2], pos = 0
輸出:tail connects to node index 0
解釋:鏈表中有一個環,其尾部連接到第一個節點。
示例 3:
輸出:no cycle
解釋:鏈表中沒有環。
代碼
雙指針
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode detectCycle(ListNode head) {
boolean isCycle = false;
ListNode a = head;//慢指針
ListNode b = head;//快指針
while(b != null && b.next != null){
a = a.next;
b = b.next.next;
if(a == b){
isCycle = true;
break;
}
}
if(isCycle){
ListNode c = head;
while(c != a){
c = c.next;
a = a.next;
}
return a;
}else return null;
}
}
set判斷重複
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode detectCycle(ListNode head) {
Set<ListNode> set = new HashSet<>();
while(head != null){
if(set.contains(head)) return head;
set.add(head);
head = head.next;
}
return null;
}
}
【鏈表】reverse-nodes-in-k-group(k個一組翻轉鏈表)
給出一個鏈表,每 k 個節點一組進行翻轉,並返回翻轉後的鏈表。
k 是一個正整數,它的值小於或等於鏈表的長度。如果節點總數不是 k 的整數倍,那麼將最後剩餘節點保持原有順序。
示例 :
給定這個鏈表:1->2->3->4->5
當 k = 2 時,應當返回: 2->1->4->3->5
當 k = 3 時,應當返回: 3->2->1->4->5
代碼
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
ListNode tmp = new ListNode(-1);
ListNode pre = tmp;
ListNode cur = pre;
tmp.next = head;
int num = 0;
while(cur != null){
cur = cur.next;
num ++;
}
while(num > k){
cur = pre.next;
for(int i = 1; i < k; i ++){
ListNode t = cur.next;
cur.next = t.next;
t.next = pre.next;
pre.next = t;
}
pre = cur;
num -= k;
}
return tmp.next;
}
}
【棧】valid-parentheses(有效的括號)
給定一個只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串,判斷字符串是否有效。
有效字符串需滿足:
- 左括號必須用相同類型的右括號閉合。
- 左括號必須以正確的順序閉合。
注意空字符串可被認爲是有效字符串。
示例 1:
輸入: "()"
輸出: true
示例 2:
輸入: "()[]{}"
輸出: true
示例 3:
輸入: "(]"
輸出: false
示例 4:
輸入: "([)]"
輸出: false
示例 5:
輸入: "{[]}"
輸出: true
代碼
class Solution {
public boolean isValid(String s) {
Map<Character, Character> map = new HashMap<>();
map.put(')', '(');
map.put('}', '{');
map.put(']', '[');
Stack<Character> stack = new Stack<>();
char[] arr = s.toCharArray();
for(char c : arr){
if(!map.containsKey(c)) stack.push(c);
else if(stack.isEmpty() || map.get(c) != stack.pop()) return false;
}
return stack.isEmpty();
}
}
【隊列&棧】implement-stack-using-queues(用隊列實現棧)
使用隊列實現棧的下列操作:
- push(x) -- 元素 x 入棧
- pop() -- 移除棧頂元素
- top() -- 獲取棧頂元素
- empty() -- 返回棧是否爲空
代碼
class MyStack {
Queue<Integer> q;
/** Initialize your data structure here. */
public MyStack() {
q = new LinkedList<>();
}
/** Push element x onto stack. */
public void push(int x) {
q.offer(x);
int n = q.size();
for(int i = 0; i < n - 1; i ++){
q.offer(q.poll());
}
}
/** Removes the element on top of the stack and returns that element. */
public int pop() {
return q.poll();
}
/** Get the top element. */
public int top() {
return q.peek();
}
/** Returns whether the stack is empty. */
public boolean empty() {
return q.isEmpty();
}
}
【隊列&棧】Implement Queue using Stacks(用棧實現隊列)
使用棧實現隊列的下列操作:
- push(x) -- 將一個元素放入隊列的尾部。
- pop() -- 從隊列首部移除元素。
- peek() -- 返回隊列首部的元素。
- empty() -- 返回隊列是否爲空。
代碼
class MyQueue {
Stack<Integer> s1, s2;
/** Initialize your data structure here. */
public MyQueue() {
s1 = new Stack<>();
s2 = new Stack<>();
}
/** Push element x to the back of queue. */
public void push(int x) {
s1.push(x);
}
/** Removes the element from in front of queue and returns that element. */
public int pop() {
if(!s2.isEmpty()) return s2.pop();
else{
while(!s1.isEmpty()){
s2.push(s1.pop());
}
return s2.pop();
}
}
/** Get the front element. */
public int peek() {
if(!s2.isEmpty()) return s2.peek();
else{
while(!s1.isEmpty()){
s2.push(s1.pop());
}
return s2.peek();
}
}
/** Returns whether the queue is empty. */
public boolean empty() {
return s1.isEmpty() && s2.isEmpty();
}
}
【優先隊列】kth-largest-element-in-a-stream(數據流中的第K大元素)
設計一個找到數據流中第K大元素的類(class)。注意是排序後的第K大元素,不是第K個不同的元素。
你的 KthLargest
類需要一個同時接收整數k
和整數數組nums
的構造器,它包含數據流中的初始元素。每次調用 KthLargest.add
,返回當前數據流中第K
大的元素。
示例:
int k = 3;
int[] arr = [4,5,8,2];
KthLargest kthLargest = new KthLargest(3, arr);
kthLargest.add(3); // returns 4
kthLargest.add(5); // returns 5
kthLargest.add(10); // returns 5
kthLargest.add(9); // returns 8
kthLargest.add(4); // returns 8
說明:
你可以假設 nums 的長度≥ k-1 且k ≥ 1。
代碼
class KthLargest {
PriorityQueue<Integer> q;
int k;
public KthLargest(int k, int[] nums) {
this.k = k;
q = new PriorityQueue<>();
for(int i : nums) add(i);
}
public int add(int val) {
if(q.size() < k) q.offer(val);
else if(val > q.peek()){
q.poll();
q.offer(val);
}
return q.peek();
}
}
【優先隊列】sliding-window-maximum(滑動窗口最大值)
給定一個數組 nums,有一個大小爲 k 的滑動窗口從數組的最左側移動到數組的最右側。你只可以看到在滑動窗口 k 內的數字。滑動窗口每次只向右移動一位。
返回滑動窗口最大值。
示例:
輸入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
輸出: [3,3,5,5,6,7]
解釋:
滑動窗口的位置 最大值
--------------- -----
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7
注意:
你可以假設 k 總是有效的,1 ≤ k ≤ 輸入數組的大小,且輸入數組不爲空。
代碼
優先隊列
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if(nums.length == 0) return new int[0];
int[] res = new int[nums.length - k + 1];
PriorityQueue<Integer> q = new PriorityQueue<>(k, Comparator.reverseOrder());
for(int i = 0; i < k; i ++){
q.offer(nums[i]);
}
res[0] = q.peek();
int index = 1;
for(int i = k; i < nums.length; i ++){
q.remove(nums[index - 1]);
q.offer(nums[i]);
res[index++] = q.peek();
}
return res;
}
}
雙端隊列
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if(nums.length == 0) return new int[0];
int[] res = new int[nums.length - k + 1];
Deque<Integer> q = new LinkedList<>();
int index = 0;
for(int i = 0; i < nums.length; i ++){
while(!q.isEmpty() && nums[q.peekLast()] <= nums[i]){
q.pollLast();
}
q.offerLast(i);
if(q.peek() == i - k) q.pollFirst();
if(i >= k - 1) res[index ++] = nums[q.peek()];
}
return res;
}
}
【哈希】valid-anagram(有效的字母異位詞)
給定兩個字符串 s 和 t ,編寫一個函數來判斷 t 是否是 s 的一個字母異位詞。
示例 1:
輸入: s = "anagram", t = "nagaram"
輸出: true
示例 2:
輸入: s = "rat", t = "car"
輸出: false
說明:
你可以假設字符串只包含小寫字母。
代碼
hashmap
class Solution {
public boolean isAnagram(String s, String t) {
Map<Character, Integer> sMap = new HashMap<>();
Map<Character, Integer> tMap = new HashMap<>();
char[] sC = s.toCharArray();
char[] sT = t.toCharArray();
for(char c : sC){
sMap.put(c, sMap.getOrDefault(c, 0) + 1);
}
for(char c : sT){
tMap.put(c, tMap.getOrDefault(c, 0) + 1);
}
return sMap.equals(tMap);
}
}
數組映射
class Solution {
public boolean isAnagram(String s, String t) {
int[] a = new int[26];
int[] b = new int[26];
char[] sC = s.toCharArray();
char[] tC = t.toCharArray();
for(char c : sC){
a[c - 'a'] += 1;
}
for(char c : tC){
b[c - 'a'] += 1;
}
return Arrays.equals(a, b);
}
}
【哈希】two-sum(兩數之和)
給定一個整數數組 nums
和一個目標值 target
,請你在該數組中找出和爲目標值的那 兩個 整數,並返回他們的數組下標。
你可以假設每種輸入只會對應一個答案。但是,你不能重複利用這個數組中同樣的元素。
示例:
給定 nums = [2, 7, 11, 15], target = 9
因爲 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
代碼
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for(int i = 0; i < nums.length; i ++){
if(map.containsKey(target - nums[i])) {
return new int[]{map.get(target - nums[i]), i};
}
map.put(nums[i], i);
}
return new int[2];
}
}
【哈希】3sum(三數之和)
給定一個包含 n 個整數的數組 nums
,判斷 nums
中是否存在三個元素 a,b,c ,使得 a + b + c = 0 ?找出所有滿足條件且不重複的三元組。
注意:答案中不可以包含重複的三元組。
例如, 給定數組 nums = [-1, 0, 1, 2, -1, -4],
滿足要求的三元組集合爲:
[
[-1, 0, 1],
[-1, -1, 2]
]
代碼
排序後枚舉
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
Set<List<Integer>> res = new HashSet<>();
for(int i = 0; i < nums.length - 1; i ++){
if(i > 0 && nums[i] == nums[i - 1]) continue;
Set<Integer> set = new HashSet<>();
for(int j = i + 1; j < nums.length; j ++){
if(!set.contains(nums[j])) set.add(-nums[i]-nums[j]);
else {
res.add(Arrays.asList(nums[i], -nums[i] - nums[j], nums[j]));
}
}
}
return new ArrayList<>(res);
}
}
排序後使用雙指針
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> res = new ArrayList<>();
for(int i = 0; i < nums.length - 1; i ++){
if(i > 0 && nums[i] == nums[i - 1]) continue;
int start = i + 1;
int end = nums.length - 1;
while(start < end){
int sum = nums[i] + nums[start] + nums[end];
if(sum > 0) end --;
else if(sum < 0) start ++;
else {
res.add(Arrays.asList(nums[i], nums[start], nums[end]));
//判重
while(start < end && nums[start] == nums[start + 1]) start ++;
while(start < end && nums[end] == nums[end - 1]) end --;
start ++;
end --;
}
}
}
return res;
}
}
【二叉樹】validate-binary-search-tree(驗證二叉搜索樹)
給定一個二叉樹,判斷其是否是一個有效的二叉搜索樹。
假設一個二叉搜索樹具有如下特徵:
- 節點的左子樹只包含小於當前節點的數。
- 節點的右子樹只包含大於當前節點的數。
- 所有左子樹和右子樹自身必須也是二叉搜索樹。
示例 1:
輸入:
2
/ \
1 3
輸出: true
示例 2:
輸入:
5
/ \
1 4
/ \
3 6
輸出: false
解釋: 輸入爲: [5,1,4,null,null,3,6]。
根節點的值爲 5 ,但是其右子節點值爲 4 。
代碼
利用中序遍歷的有序性
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
List<Integer> list = new ArrayList<>();
public boolean isValidBST(TreeNode root) {
midOrder(root);
if(list.size() == 0) return true;
int tmp = list.get(0);
for(int i = 1,length = list.size(); i < length; i ++){
if(list.get(i) <= tmp){
return false;
}
tmp = list.get(i);
}
return true;
}
public void midOrder(TreeNode root){
if(root != null){
midOrder(root.left);
list.add(root.val);
midOrder(root.right);
}
}
}
遞歸
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
TreeNode pre = null;
public boolean isValidBST(TreeNode root) {
if(root == null) return true;
if(!isValidBST(root.left)) return false;
if(pre != null && pre.val >= root.val) return false;
pre = root;
return isValidBST(root.right);
}
}
【二叉樹】lowest-common-ancestor-of-a-binary-search-tree(二叉搜索樹的最近公共祖先)
給定一個二叉搜索樹, 找到該樹中兩個指定節點的最近公共祖先。
百度百科中最近公共祖先的定義爲:“對於有根樹 T 的兩個結點 p、q,最近公共祖先表示爲一個結點 x,滿足 x 是 p、q 的祖先且 x 的深度儘可能大(一個節點也可以是它自己的祖先)。”
例如,給定如下二叉搜索樹: root = [6,2,8,0,4,7,9,null,null,3,5]
代碼
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
while(root != null){
if(p.val > root.val && q.val > root.val) root = root.right;
else if(p.val < root.val && q.val < root.val) root = root.left;
else return root;
}
return null;
}
}
【遞歸&分治】Pow(x, n)
實現 pow(x, n) ,即計算 x 的 n 次冪函數。
示例 1:
輸入: 2.00000, 10
輸出: 1024.00000
示例 2:
輸入: 2.10000, 3
輸出: 9.26100
示例 3:
輸入: 2.00000, -2
輸出: 0.25000
解釋: 2-2 = 1/22 = 1/4 = 0.25
說明:
- -100.0 < x < 100.0
- n 是 32 位有符號整數,其數值範圍是 [−231, 231 − 1] 。
代碼
class Solution {
public double myPow(double x, int n) {
if (n < 0) return 1.0 / myPow(x, -n);
if (n == 0) return 1;
double v = myPow(x, n / 2);
return n % 2 == 0 ? v * v : v * v * x;
}
}
【計數】majority-element(求衆數)
給定一個大小爲 n 的數組,找到其中的衆數。衆數是指在數組中出現次數大於 ⌊ n/2 ⌋
的元素。
你可以假設數組是非空的,並且給定的數組總是存在衆數。
示例 1:
輸入: [3,2,3]
輸出: 3
示例 2:
輸入: [2,2,1,1,1,2,2]
輸出: 2
代碼
利用map計數
class Solution {
public int majorityElement(int[] nums) {
Map<Integer, Integer> map = new HashMap<>();
for(int i = 0, length = nums.length; i < length; i ++){
if(map.containsKey(nums[i])){
map.put(nums[i], map.get(nums[i]) + 1);
}else{
map.put(nums[i], 1);
}
}
Set<Integer> set = map.keySet();
for(Integer key : set){
if(map.get(key) > nums.length/2) return key;
}
return 0;
}
}
排序計數
class Solution {
public int majorityElement(int[] nums) {
if(nums.length == 1) return nums[0];
Arrays.sort(nums);
int count = 1;
for(int i = 1; i < nums.length; i ++){
if(nums[i] == nums[i-1]){
count ++;
} else count = 1;
if(count > nums.length / 2) return nums[i];
}
return 0;
}
}
【貪心】best-time-to-buy-and-sell-stock-ii(買賣股票的最佳時機 II)
給定一個數組,它的第 i 個元素是一支給定股票第 i 天的價格。
設計一個算法來計算你所能獲取的最大利潤。你可以儘可能地完成更多的交易(多次買賣一支股票)。
注意:你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。
示例 1:
輸入: [7,1,5,3,6,4]
輸出: 7
解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 3 天(股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。
隨後,在第 4 天(股票價格 = 3)的時候買入,在第 5 天(股票價格 = 6)的時候賣出, 這筆交易所能獲得利潤 = 6-3 = 3 。
示例 2:
輸入: [1,2,3,4,5]
輸出: 4
解釋: 在第 1 天(股票價格 = 1)的時候買入,在第 5 天 (股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接連購買股票,之後再將它們賣出。
因爲這樣屬於同時參與了多筆交易,你必須在再次購買前出售掉之前的股票。
示例 3:
輸入: [7,6,4,3,1]
輸出: 0
解釋: 在這種情況下, 沒有交易完成, 所以最大利潤爲 0。
代碼
class Solution {
public int maxProfit(int[] prices) {
if(prices == null || prices.length == 0) return 0;
int cur = prices[0];
int length = prices.length;
int res = 0;
for(int i = 0; i < length; i ++){
if(prices[i] > cur){
res += (prices[i] - cur);
cur = prices[i];
}else{
cur = prices[i];
}
}
return res;
}
}
【BFS】 廣度優先搜索 (僞碼)
BFS()
{
輸入起始點;
初始化所有頂點標記爲未遍歷;
初始化一個隊列queue並將起始點放入隊列;
while(queue不爲空)
{
從隊列中刪除一個頂點s並標記爲已遍歷;
將s鄰接的所有還沒遍歷的點加入隊列;
}
}
【DFS】 深度優先搜索(僞碼)
DFS(頂點v)
{
標記v爲已遍歷;
for(對於每一個鄰接v且未標記遍歷的點u)
DFS(u);
}
【BFS】binary-tree-level-order-traversal(二叉樹的層次遍歷)
給定一個二叉樹,返回其按層次遍歷的節點值。 (即逐層地,從左到右訪問所有節點)。
例如:
給定二叉樹: [3,9,20,null,null,15,7]
,
3
/ \
9 20
/ \
15 7
返回其層次遍歷結果:
[
[3],
[9,20],
[15,7]
]
代碼
bfs
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
if(root == null) return new ArrayList<>();
Queue<TreeNode> queue = new LinkedList<>();
List<List<Integer>> result = new ArrayList<>();
queue.offer(root);
while (!queue.isEmpty()){
List<Integer> node = new ArrayList<>();
int length = queue.size();
while (length > 0){
TreeNode tree = queue.poll();
if(tree.left != null){
queue.offer(tree.left);
}
if(tree.right != null){
queue.offer(tree.right);
}
node.add(tree.val);
length --;
}
result.add(node);
}
return result;
}
}
也可以用dfs
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
if(root == null) return new ArrayList<>();
List<List<Integer>> res = new ArrayList<>();
_dfs(res, root, 0);
return res;
}
public void _dfs(List<List<Integer>> list, TreeNode node, int level){
if(node == null) return;
if(list.size() < level + 1){
list.add(new ArrayList<>());
}
list.get(level).add(node.val);
_dfs(list, node.left, level + 1);
_dfs(list, node.right, level + 1);
}
}
【DFS】maximum-depth-of-binary-tree(二叉樹的最大深度)
給定一個二叉樹,找出其最大深度。
二叉樹的深度爲根節點到最遠葉子節點的最長路徑上的節點數。
說明: 葉子節點是指沒有子節點的節點。
示例:
給定二叉樹 [3,9,20,null,null,15,7]
,
3
/ \
9 20
/ \
15 7
返回它的最大深度 3 。
代碼
DFS
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public int maxDepth(TreeNode root) {
return root == null ? 0 : Math.max(maxDepth(root.left),maxDepth(root.right)) + 1;
}
}
BFS
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public int maxDepth(TreeNode root) {
if(root == null) return 0;
Queue<TreeNode> q = new LinkedList<>();
q.offer(root);
int count = 1;
while(!q.isEmpty()){
int length = q.size();
boolean flag = false;
for(int i = 0; i < length; i ++){
TreeNode node = q.poll();
if(node.left != null || node.right != null) flag = true;
if(node.left != null) q.offer(node.left);
if(node.right != null) q.offer(node.right);
}
if(flag) count ++;
}
return count;
}
}
【DFS】minimum-depth-of-binary-tree(二叉樹的最小深度)
給定一個二叉樹,找出其最小深度。
最小深度是從根節點到最近葉子節點的最短路徑上的節點數量。
說明: 葉子節點是指沒有子節點的節點。
示例:
給定二叉樹 [3,9,20,null,null,15,7]
,
3
/ \
9 20
/ \
15 7
返回它的最小深度 2.
代碼
BFS
class Solution {
public int minDepth(TreeNode root) {
if(root == null) return 0;
Queue<TreeNode> q = new LinkedList<>();
q.offer(root);
int count = 1;
while(!q.isEmpty()){
int length = q.size();
boolean flag = false;
for(int i = 0; i < length; i ++){
TreeNode node = q.poll();
if(node.left == null && node.right == null) return count;
if(node.left != null || node.right != null) flag = true;
if(node.left != null) q.offer(node.left);
if(node.right != null) q.offer(node.right);
}
if(flag) count ++;
}
return count;
}
}
DFS
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public int minDepth(TreeNode root) {
if(null == root) return 0;
if(null == root.left) return minDepth(root.right) + 1;
if(null == root.right) return minDepth(root.left) + 1;
return Math.min(minDepth(root.left),minDepth(root.right)) + 1;
}
}
【回溯】generate-parentheses(括號生成)
給出 n 代表生成括號的對數,請你寫出一個函數,使其能夠生成所有可能的並且有效的括號組合。
例如,給出 n = 3,生成結果爲:
[
"((()))",
"(()())",
"(())()",
"()(())",
"()()()"
]
代碼
class Solution {
public List<String> generateParenthesis(int n) {
List<String> res = new ArrayList<>();
_gen(res, 0, 0, n, "");
return res;
}
public void _gen(List<String> list, int left, int right, int n, String res){
if(left == n && right == n){
list.add(res);
return;
}
if(left < n){
_gen(list, left + 1, right, n, res + "(");
}
if(left > right && right < n){
_gen(list, left, right + 1, n, res + ")");
}
}
}
【剪枝】n-queens(N皇后)
n 皇后問題研究的是如何將 n 個皇后放置在 n×n 的棋盤上,並且使皇后彼此之間不能相互攻擊。
上圖爲 8 皇后問題的一種解法。
給定一個整數 n,返回所有不同的 n 皇后問題的解決方案。
每一種解法包含一個明確的 n 皇后問題的棋子放置方案,該方案中 'Q'
和 '.'
分別代表了皇后和空位。
示例:
輸入: 4
輸出: [
[".Q..", // 解法 1
"...Q",
"Q...",
"..Q."],
["..Q.", // 解法 2
"Q...",
"...Q",
".Q.."]
]
解釋: 4 皇后問題存在兩個不同的解法。
代碼
class Solution {
private List<List<String>> res = new ArrayList<>();
private Set<Integer> cols = new HashSet<>();
private Set<Integer> pie = new HashSet<>();
private Set<Integer> na = new HashSet<>();
public List<List<String>> solveNQueens(int n) {
if (n < 1) {
return new ArrayList<>();
}
dfs(n, 0, new ArrayList<>());
return res;
}
public void dfs(int n, int row, List<String> curState) {
if (row >= n) {
res.add(curState);
return;
}
for (int i = 0; i < n; i ++) {
if (cols.contains(i) || pie.contains(row + i) || na.contains(row - i)) {
continue;
}
cols.add(i);
pie.add(row + i);
na.add(row - i);
StringBuilder sb = new StringBuilder();
for(int j = 0; j < n; j ++) {
if (j == i) {
sb.append("Q");
} else {
sb.append(".");
}
}
List<String> tmp = new ArrayList(curState);
tmp.add(sb.toString());
dfs(n, row + 1, tmp);
cols.remove(i);
pie.remove(row + i);
na.remove(row - i);
}
}
}
【哈希】valid-sudoku(有效的數獨)
判斷一個 9x9 的數獨是否有效。只需要根據以下規則,驗證已經填入的數字是否有效即可。
- 數字
1-9
在每一行只能出現一次。 - 數字
1-9
在每一列只能出現一次。 - 數字
1-9
在每一個以粗實線分隔的3x3
宮內只能出現一次。
上圖是一個部分填充的有效的數獨。
數獨部分空格內已填入了數字,空白格用 '.'
表示。
示例 1:
輸入:
[
["5","3",".",".","7",".",".",".","."],
["6",".",".","1","9","5",".",".","."],
[".","9","8",".",".",".",".","6","."],
["8",".",".",".","6",".",".",".","3"],
["4",".",".","8",".","3",".",".","1"],
["7",".",".",".","2",".",".",".","6"],
[".","6",".",".",".",".","2","8","."],
[".",".",".","4","1","9",".",".","5"],
[".",".",".",".","8",".",".","7","9"]
]
輸出: true
示例 2:
輸入:
[
["8","3",".",".","7",".",".",".","."],
["6",".",".","1","9","5",".",".","."],
[".","9","8",".",".",".",".","6","."],
["8",".",".",".","6",".",".",".","3"],
["4",".",".","8",".","3",".",".","1"],
["7",".",".",".","2",".",".",".","6"],
[".","6",".",".",".",".","2","8","."],
[".",".",".","4","1","9",".",".","5"],
[".",".",".",".","8",".",".","7","9"]
]
輸出: false
解釋: 除了第一行的第一個數字從 5 改爲 8 以外,空格內其他數字均與 示例1 相同。
但由於位於左上角的 3x3 宮內有兩個 8 存在, 因此這個數獨是無效的。
說明:
- 一個有效的數獨(部分已被填充)不一定是可解的。
- 只需要根據以上規則,驗證已經填入的數字是否有效即可。
- 給定數獨序列只包含數字
1-9
和字符'.'
。 - 給定數獨永遠是
9x9
形式的。
代碼
class Solution {
public boolean isValidSudoku(char[][] board) {
// 記錄某行,某位數字是否已經被擺放
boolean[][] row = new boolean[9][9];
// 記錄某列,某位數字是否已經被擺放
boolean[][] col = new boolean[9][9];
// 記錄某 3x3 宮格內,某位數字是否已經被擺放
boolean[][] block = new boolean[9][9];
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
if (board[i][j] != '.') {
int num = board[i][j] - '1';
int blockIndex = i / 3 * 3 + j / 3;
if (row[i][num] || col[j][num] || block[blockIndex][num]) {
return false;
} else {
row[i][num] = true;
col[j][num] = true;
block[blockIndex][num] = true;
}
}
}
}
return true;
}
}
【剪枝&回溯】sudoku-solver(解數獨)
編寫一個程序,通過已填充的空格來解決數獨問題。
一個數獨的解法需遵循如下規則:
- 數字
1-9
在每一行只能出現一次。 - 數字
1-9
在每一列只能出現一次。 - 數字
1-9
在每一個以粗實線分隔的3x3
宮內只能出現一次。
空白格用 '.'
表示。
一個數獨。
答案被標成紅色。
Note:
- 給定的數獨序列只包含數字
1-9
和字符'.'
。 - 你可以假設給定的數獨只有唯一解。
- 給定數獨永遠是
9x9
形式的。
代碼
class Solution {
public void solveSudoku(char[][] board) {
if (board == null || board.length == 0) return;
solve(board);
}
private boolean solve(char[][] board) {
for (int i = 0; i < board.length; i ++) {
for (int j = 0; j < board[0].length; j ++) {
if (board[i][j] == '.') {
for (char c = '1'; c <= '9'; c ++) {
if (isValid(board, i, j, c)) {
board[i][j] = c;
if (solve(board)) {
return true;
} else {
board[i][j] = '.';//還原
}
}
}
return false;
}
}
}
return true;
}
private boolean isValid(char[][] board, int row, int col, char c) {
for (int i = 0; i < 9; i ++) {
if(board[i][col] != '.' && board[i][col] == c) return false;
if(board[row][i] != '.' && board[row][i] == c) return false;
int blocki = 3 * (row / 3) + i / 3;
int blockj = 3 * (col / 3) + i % 3;
if(board[blocki][blockj] != '.' && board[blocki][blockj] == c) return false;
}
return true;
}
}
【二分查找】返回指定元素下標(基本用法)
public int getTargetIndex(int[] arr, int target) {
int left = 0;
int right = arr.length - 1;
while (left <= right) {
int mid = (left + right) / 2;
if (arr[mid] == target) {
return mid;
} else if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
【二分查找】sqrtx(x 的平方根)
實現 int sqrt(int x)
函數。
計算並返回 x 的平方根,其中 x 是非負整數。
由於返回類型是整數,結果只保留整數的部分,小數部分將被捨去。
示例 1:
輸入: 4
輸出: 2
示例 2:
輸入: 8
輸出: 2
說明: 8 的平方根是 2.82842...,
由於返回類型是整數,小數部分將被捨去。
代碼
class Solution {
public int mySqrt(int x) {
if(x == 0 || x == 1) return x;
int left = 1;
int right = x;
while (left <= right) {
int mid = (left + right) / 2;
if (mid == x / mid) {
return mid;
} else if (mid > x / mid) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return left - 1;
}
}
【二分查找】valid-perfect-square(有效的完全平方數)
給定一個正整數 num,編寫一個函數,如果 num 是一個完全平方數,則返回 True,否則返回 False。
說明:不要使用任何內置的庫函數,如 sqrt
。
示例 1:
輸入:16
輸出:True
示例 2:
輸入:14
輸出:False
代碼
公式法--1+3+5+7+9+…+(2n-1)=n^2
class Solution {
public boolean isPerfectSquare(int num) {
int sum = 0;
for(int i = 1; num > 0; i +=2){
num -= i;
}
return num == 0;
}
}
二分法
class Solution {
public boolean isPerfectSquare(int num) {
if(num == 0 || num == 1) return true;
int left = 1;
int right = num;
int res = 0;
while (left <= right) {
int mid = (left + right) / 2;
if (mid == num / mid) {
res = mid;
break;
} else if (mid > num / mid) {
right = mid - 1;
} else {
left = mid + 1;
res = mid;
}
}
return res * res == num;
}
}
【字典樹】implement-trie-prefix-tree(實現 Trie (前綴樹))
實現一個 Trie (前綴樹),包含 insert
, search
, 和 startsWith
這三個操作。
示例:
Trie trie = new Trie();
trie.insert("apple");
trie.search("apple"); // 返回 true
trie.search("app"); // 返回 false
trie.startsWith("app"); // 返回 true
trie.insert("app");
trie.search("app"); // 返回 true
說明:
- 你可以假設所有的輸入都是由小寫字母
a-z
構成的。 - 保證所有輸入均爲非空字符串。
代碼
class Trie {
Trie[] children = new Trie[26];
boolean isEndOfWord = false;
/** Initialize your data structure here. */
public Trie() {
}
/** Inserts a word into the trie. */
public void insert(String word) {
char[] arr = word.toCharArray();
Trie[] childs = children;
for (int i = 0; i < arr.length; i ++) {
if (childs[arr[i] - 'a'] == null) {
childs[arr[i] - 'a'] = new Trie();
}
if (i == arr.length - 1) {
childs[arr[i] - 'a'].isEndOfWord = true;
}
childs = childs[arr[i] - 'a'].children;
}
}
/** Returns if the word is in the trie. */
public boolean search(String word) {
char[] arr = word.toCharArray();
Trie[] childs = children;
for (int i = 0; i < arr.length; i ++) {
if (childs[arr[i] - 'a'] == null) {
return false;
}
if (i == arr.length - 1 && !childs[arr[i] - 'a'].isEndOfWord) {
return false;
}
childs = childs[arr[i] - 'a'].children;
}
return true;
}
/** Returns if there is any word in the trie that starts with the given prefix. */
public boolean startsWith (String prefix) {
char[] arr = prefix.toCharArray();
Trie[] childs = children;
for (int i = 0; i < arr.length; i ++) {
if (childs[arr[i] - 'a'] == null) {
return false;
}
childs = childs[arr[i] - 'a'].children;
}
return true;
}
}
/**
* Your Trie object will be instantiated and called as such:
* Trie obj = new Trie();
* obj.insert(word);
* boolean param_2 = obj.search(word);
* boolean param_3 = obj.startsWith(prefix);
*/
【字典樹】word-search-ii(單詞搜索 II)
給定一個二維網格 board 和一個字典中的單詞列表 words,找出所有同時在二維網格和字典中出現的單詞。
單詞必須按照字母順序,通過相鄰的單元格內的字母構成,其中“相鄰”單元格是那些水平相鄰或垂直相鄰的單元格。同一個單元格內的字母在一個單詞中不允許被重複使用。
示例:
輸入:
words = ["oath","pea","eat","rain"] and board =
[
['o','a','a','n'],
['e','t','a','e'],
['i','h','k','r'],
['i','f','l','v']
]
輸出: ["eat","oath"]
說明:
你可以假設所有輸入都由小寫字母 a-z
組成。
提示:
- 你需要優化回溯算法以通過更大數據量的測試。你能否早點停止回溯?
- 如果當前單詞不存在於所有單詞的前綴中,則可以立即停止回溯。什麼樣的數據結構可以有效地執行這樣的操作?散列表是否可行?爲什麼? 前綴樹如何?如果你想學習如何實現一個基本的前綴樹,請先查看這個問題: 實現Trie(前綴樹)。
代碼
class Solution {
Set<String> res = new HashSet<>();
public List<String> findWords(char[][] board, String[] words) {
Trie trie = new Trie();
for (String word : words) {
trie.insert(word);
}
int m = board.length;
int n = board[0].length;
boolean[][] visited = new boolean[m][n];
for (int i = 0; i < m; i ++) {
for (int j = 0; j < n; j ++) {
dfs(board, visited, "", i, j, trie);
}
}
return new ArrayList<>(res);
}
private void dfs(char[][] board, boolean[][] visited, String str, int x, int y, Trie trie) {
if (x < 0 || x >= board.length || y < 0 || y >= board[0].length) return;
if (visited[x][y]) return;
str += board[x][y];
if (!trie.startsWith(str)) return;
if (trie.search(str)) {
res.add(str);
}
visited[x][y] = true;
dfs(board, visited, str, x - 1, y, trie);
dfs(board, visited, str, x + 1, y, trie);
dfs(board, visited, str, x, y - 1, trie);
dfs(board, visited, str, x, y + 1, trie);
visited[x][y] = false;
}
}
class Trie {
Trie[] children = new Trie[26];
boolean isEndOfWord = false;
/** Initialize your data structure here. */
public Trie() {
}
/** Inserts a word into the trie. */
public void insert(String word) {
char[] arr = word.toCharArray();
Trie[] childs = children;
for (int i = 0; i < arr.length; i ++) {
if (childs[arr[i] - 'a'] == null) {
childs[arr[i] - 'a'] = new Trie();
}
if (i == arr.length - 1) {
childs[arr[i] - 'a'].isEndOfWord = true;
}
childs = childs[arr[i] - 'a'].children;
}
}
/** Returns if the word is in the trie. */
public boolean search(String word) {
char[] arr = word.toCharArray();
Trie[] childs = children;
for (int i = 0; i < arr.length; i ++) {
if (childs[arr[i] - 'a'] == null) {
return false;
}
if (i == arr.length - 1 && !childs[arr[i] - 'a'].isEndOfWord) {
return false;
}
childs = childs[arr[i] - 'a'].children;
}
return true;
}
/** Returns if there is any word in the trie that starts with the given prefix. */
public boolean startsWith (String prefix) {
char[] arr = prefix.toCharArray();
Trie[] childs = children;
for (int i = 0; i < arr.length; i ++) {
if (childs[arr[i] - 'a'] == null) {
return false;
}
childs = childs[arr[i] - 'a'].children;
}
return true;
}
}
【位運算】number-of-1-bits(位1的個數)
編寫一個函數,輸入是一個無符號整數,返回其二進制表達式中數字位數爲 ‘1’ 的個數(也被稱爲漢明重量)。
示例 1:
輸入:00000000000000000000000000001011
輸出:3
解釋:輸入的二進制串 00000000000000000000000000001011 中,共有三位爲 '1'。
示例 2:
輸入:00000000000000000000000010000000
輸出:1
解釋:輸入的二進制串 00000000000000000000000010000000 中,共有一位爲 '1'。
示例 3:
輸入:11111111111111111111111111111101
輸出:31
解釋:輸入的二進制串 11111111111111111111111111111101 中,共有 31 位爲 '1'。
提示:
- 請注意,在某些語言(如 Java)中,沒有無符號整數類型。在這種情況下,輸入和輸出都將被指定爲有符號整數類型,並且不應影響您的實現,因爲無論整數是有符號的還是無符號的,其內部的二進制表示形式都是相同的。
- 在 Java 中,編譯器使用二進制補碼記法來表示有符號整數。因此,在上面的 示例 3 中,輸入表示有符號整數
-3
。
代碼
遍歷每一位是否爲1
public class Solution {
// you need to treat n as an unsigned value
public int hammingWeight(int n) {
int count = 0;
for (int i = 0; i < 32; i ++) {
count += n & 1;
n >>= 1;
}
return count;
}
}
逐步打掉最後的1
public class Solution {
// you need to treat n as an unsigned value
public int hammingWeight(int n) {
int count = 0;
while (n != 0) {
count ++;
n = n & (n-1);
}
return count;
}
}
【位運算】power-of-two(2的冪)
給定一個整數,編寫一個函數來判斷它是否是 2 的冪次方。
示例 1:
輸入: 1
輸出: true
解釋: 20 = 1
示例 2:
輸入: 16
輸出: true
解釋: 24 = 16
示例 3:
輸入: 218
輸出: false
代碼
迭代判斷
class Solution {
public boolean isPowerOfTwo(int n) {
if (n < 1) return false;
while (n > 1) {
if(n % 2 != 0) return false;
n /= 2;
}
return true;
}
}
位運算(2的冪二進制只有一個1)
class Solution {
public boolean isPowerOfTwo(int n) {
return n > 0 && (n & (n - 1)) == 0;
}
}
【位運算】counting-bits(比特位計數)
給定一個非負整數 num。對於 0 ≤ i ≤ num 範圍中的每個數字 i ,計算其二進制數中的 1 的數目並將它們作爲數組返回。
示例 1:
輸入: 2
輸出: [0,1,1]
示例 2:
輸入: 5
輸出: [0,1,1,2,1,2]
進階:
- 給出時間複雜度爲O(n*sizeof(integer))的解答非常容易。但你可以在線性時間O(n)內用一趟掃描做到嗎?
- 要求算法的空間複雜度爲O(n)。
- 你能進一步完善解法嗎?要求在C++或任何其他語言中不使用任何內置函數(如 C++ 中的 __builtin_popcount)來執行此操作。
代碼
求每個數的1的數目
class Solution {
public int[] countBits(int num) {
int[] res = new int[num + 1];
for (int i = 0; i <= num; i ++) {
res[i] = get1num(i);
}
return res;
}
private int get1num(int num) {
int count = 0;
while (num != 0) {
count ++;
num &= num-1;
}
return count;
}
}
利用位運算 i&(i-1)要比i少一個1
class Solution {
public int[] countBits(int num) {
int[] res = new int[num + 1];
for (int i = 1; i <= num; i ++) {
res[i] = res[i & (i - 1)] + 1;
}
return res;
}
}
【動態規劃】fibonacci-number(斐波那契數)
斐波那契數,通常用 F(n)
表示,形成的序列稱爲斐波那契數列。該數列由 0
和 1
開始,後面的每一項數字都是前面兩項數字的和。也就是:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
給定 N
,計算 F(N)
。
示例 1:
輸入:2
輸出:1
解釋:F(2) = F(1) + F(0) = 1 + 0 = 1.
示例 2:
輸入:3
輸出:2
解釋:F(3) = F(2) + F(1) = 1 + 1 = 2.
示例 3:
輸入:4
輸出:3
解釋:F(4) = F(3) + F(2) = 2 + 1 = 3.
代碼
遞歸
public int fib(int N) {
if(N < 2) return N;
return fib(N - 1) + fib(N - 2);
}
動態規劃
public int fib(int N) {
//dp[N] = dp[N - 1] + dp[N - 2];
int[] dp = new int[N + 1];
if(N == 0) return 0;
dp[0] = 0;
dp[1] = 1;
for(int i = 2; i <= N; i ++){
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[N];
}
遍歷
public int fib(int N) {
if(N < 2) return N;
int a = 0;
int b = 1;
int c = 0;
for(int i = 2; i <= N; i ++){
c = a + b;
a = b;
b = c;
}
return c;
}
【動態規劃】climbing-stairs(爬樓梯)
假設你正在爬樓梯。需要 n 階你才能到達樓頂。
每次你可以爬 1 或 2 個臺階。你有多少種不同的方法可以爬到樓頂呢?
注意:給定 n 是一個正整數。
示例 1:
輸入: 2
輸出: 2
解釋: 有兩種方法可以爬到樓頂。
1. 1 階 + 1 階
2. 2 階
示例 2:
輸入: 3
輸出: 3
解釋: 有三種方法可以爬到樓頂。
1. 1 階 + 1 階 + 1 階
2. 1 階 + 2 階
3. 2 階 + 1 階
代碼
class Solution {
public int climbStairs(int n) {
int[] dp = new int[n + 1];
dp[0] = 1;
dp[1] = 1;
for (int i = 2; i <= n; i ++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
}
【動態規劃】triangle(三角形最小路徑和)
給定一個三角形,找出自頂向下的最小路徑和。每一步只能移動到下一行中相鄰的結點上。
例如,給定三角形:
[
[2],
[3,4],
[6,5,7],
[4,1,8,3]
]
自頂向下的最小路徑和爲 11
(即,2 + 3 + 5 + 1 = 11)。
說明:
如果你可以只使用 O(n) 的額外空間(n 爲三角形的總行數)來解決這個問題,那麼你的算法會很加分。
代碼
二維數組
class Solution {
public int minimumTotal(List<List<Integer>> triangle) {
//dp[i][j] = min(dp[i + 1][j], dp[i + 1][j + 1]) + triangle.get(i).get(j)
int m = triangle.size();
int[][] dp = new int[m][triangle.get(m - 1).size()];
for (int i = 0; i < triangle.get(m - 1).size(); i ++) {
dp[m - 1][i] = triangle.get(m - 1).get(i);
}
for (int i = m - 2; i >= 0; i --) {
for (int j = 0; j < triangle.get(i).size(); j ++) {
dp[i][j] = Math.min(dp[i + 1][j],
dp[i + 1][j + 1]) + triangle.get(i).get(j);
}
}
return dp[0][0];
}
}
一維數組
class Solution {
public int minimumTotal(List<List<Integer>> triangle) {
//dp[j] = Math.min(dp[j], dp[j + 1]) + triangle.get(i).get(j);
int m = triangle.size();
int n = triangle.get(m - 1).size();
int[] dp = new int[n];
for (int i = 0; i < n; i ++) {
dp[i] = triangle.get(m - 1).get(i);
}
for (int i = m - 2; i >= 0; i --) {
for (int j = 0; j < triangle.get(i).size(); j ++) {
dp[j] = Math.min(dp[j], dp[j + 1]) + triangle.get(i).get(j);
}
}
return dp[0];
}
}
【動態規劃】maximum-product-subarray(乘積最大子序列)
給定一個整數數組 nums
,找出一個序列中乘積最大的連續子序列(該序列至少包含一個數)。
示例 1:
輸入: [2,3,-2,4]
輸出: 6
解釋: 子數組 [2,3] 有最大乘積 6。
示例 2:
輸入: [-2,0,-1]
輸出: 0
解釋: 結果不能爲 2, 因爲 [-2,-1] 不是子數組。
代碼
解法1
class Solution {
public int maxProduct(int[] nums) {
int[][] dp = new int[2][2];
dp[0][1] = nums[0];//最小值
dp[0][0] = nums[0];//最大值
int res = nums[0];
for (int i = 1; i < nums.length; i ++) {
int x = i % 2;
int y = (i - 1) % 2;
dp[x][0] = Math.max(Math.max(dp[y][0] * nums[i], dp[y][1] * nums[i]), nums[i]);
dp[x][1] = Math.min(Math.min(dp[y][0] * nums[i], dp[y][1] * nums[i]), nums[i]);
res = Math.max(res, dp[x][0]);
}
return res;
}
}
解法2
class Solution {
public int maxProduct(int[] nums) {
int curMin = nums[0];//最小值
int curMax = nums[0];//最大值
int res = nums[0];
for (int i = 1; i < nums.length; i ++) {
int tmpMax = curMax * nums[i];
int tmpMin = curMin * nums[i];
curMin = Math.min(Math.min(tmpMax, tmpMin), nums[i]);
curMax = Math.max(Math.max(tmpMax, tmpMin), nums[i]);
res = Math.max(res, curMax);
}
return res;
}
}
【動態規劃】買賣股票的最佳時機
給定一個數組,它的第 i 個元素是一支給定股票第 i 天的價格。
如果你最多隻允許完成一筆交易(即買入和賣出一支股票),設計一個算法來計算你所能獲取的最大利潤。
注意你不能在買入股票前賣出股票。
示例 1:
輸入: [7,1,5,3,6,4]
輸出: 5
解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 5 天(股票價格 = 6)的時候賣出,最大利潤 = 6-1 = 5 。
注意利潤不能是 7-1 = 6, 因爲賣出價格需要大於買入價格。
示例 2:
輸入: [7,6,4,3,1]
輸出: 0
解釋: 在這種情況下, 沒有交易完成, 所以最大利潤爲 0。
代碼
循環
class Solution {
public int maxProfit(int[] prices) {
int tmp = 0;
int max = 0;
for (int i = 1; i < prices.length; i ++) {
int diff = prices[i] - prices[i - 1];
//連續差值和 如 1 5 3 6 -- (6 - 1) = (5 - 1) + (3 - 5) + (6 - 3)
tmp = tmp + diff > 0 ? tmp + diff : 0;
max = Math.max(max, tmp);
}
return max;
}
}
動態規劃
class Solution {
public int maxProfit(int[] prices) {
if (prices == null || prices.length == 0) return 0;
int res = 0;
int[][] profit = new int[prices.length][3];
profit[0][0] = 0;//沒有買入股票
profit[0][1] = -prices[0];//買入股票沒有賣出
profit[0][2] = 0;//之前買過股票,現在賣了
for (int i = 1; i < prices.length; i ++) {
profit[i][0] = profit[i - 1][0];
profit[i][1] = Math.max(profit[i - 1][1], profit[i - 1][0] - prices[i]);//前一天買入股票,或者之前沒有股票今天剛買
profit[i][2] = profit[i - 1][1] + prices[i];
res = Math.max(Math.max(res, profit[i][0]), Math.max(profit[i][1], profit[i][2]));
}
return res;
}
}
【動態規劃】買賣股票的最佳時機 III
給定一個數組,它的第 i 個元素是一支給定的股票在第 i 天的價格。
設計一個算法來計算你所能獲取的最大利潤。你最多可以完成 兩筆 交易。
注意: 你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。
示例 1:
輸入: [3,3,5,0,0,3,1,4]
輸出: 6
解釋: 在第 4 天(股票價格 = 0)的時候買入,在第 6 天(股票價格 = 3)的時候賣出,這筆交易所能獲得利潤 = 3-0 = 3 。
隨後,在第 7 天(股票價格 = 1)的時候買入,在第 8 天 (股票價格 = 4)的時候賣出,這筆交易所能獲得利潤 = 4-1 = 3 。
示例 2:
輸入: [1,2,3,4,5]
輸出: 4
解釋: 在第 1 天(股票價格 = 1)的時候買入,在第 5 天 (股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接連購買股票,之後再將它們賣出。
因爲這樣屬於同時參與了多筆交易,你必須在再次購買前出售掉之前的股票。
示例 3:
輸入: [7,6,4,3,1]
輸出: 0
解釋: 在這個情況下, 沒有交易完成, 所以最大利潤爲 0。
代碼
class Solution {
public int maxProfit(int[] prices) {
if (prices == null || prices.length == 0) return 0;
int res = 0;
//第一位代表天數,第二位代表交易次數,第三位0代表不持有股票,1代表持有股票
int[][][] profit = new int[prices.length][3][2];
for (int i = 0; i < 3; i ++) {
//初始化第1天的數據
profit[0][i][0] = 0;
profit[0][i][1] = -prices[0];
}
for (int i = 1; i < prices.length; i ++) {
for (int j = 0; j < 3; j ++) {
//不持有股票分兩種情況
//1:前一天持有,今天賣了 profit[i - 1][j - 1][1] + prices[i] 如果操作數爲0則不存在本情況
//2: 前一天就不持有,今天無操作 profit[i - 1][j][0]
profit[i][j][0] = j != 0 ? Math.max(profit[i - 1][j][0], profit[i - 1][j - 1][1] + prices[i]) : profit[i - 1][j][0];
//持有股票分兩種情況
//1: 前一天持有,今天無操作 profit[i - 1][j][1]
//2:前一天不持有,今天買入 profit[i - 1][j][0] - prices[i]
profit[i][j][1] = Math.max(profit[i - 1][j][1], profit[i - 1][j][0] - prices[i]);
res = Math.max(res, profit[i][j][0]);
}
}
return res;
}
}
【動態規劃】longest-increasing-subsequence(最長上升子序列)
給定一個無序的整數數組,找到其中最長上升子序列的長度。
示例:
輸入: [10,9,2,5,3,7,101,18]
輸出: 4
解釋: 最長的上升子序列是 [2,3,7,101],它的長度是 4。
說明:
- 可能會有多種最長上升子序列的組合,你只需要輸出對應的長度即可。
- 你算法的時間複雜度應該爲 O(n2) 。
進階: 你能將算法的時間複雜度降低到 O(n log n) 嗎?
代碼
動態規劃
class Solution {
public int lengthOfLIS(int[] nums) {
if (nums == null || nums.length == 0) return 0;
int n = nums.length;
int[] dp = new int[n];
Arrays.fill(dp, 1);
int res = 1;
for (int i = 1; i < n; i ++) {
for (int j = 0; j < i; j ++) {
if (nums[i] > nums[j]) {
dp[i] = Math.max(dp[j] + 1, dp[i]);
}
}
res = Math.max(res, dp[i]);
}
return res;
}
}
二分
class Solution {
public int lengthOfLIS(int[] nums) {
if (nums == null || nums.length == 0) return 0;
List<Integer> lis = new ArrayList<>();
lis.add(nums[0]);
for (int i = 1; i < nums.length; i ++) {
int left = 0;
int right = lis.size() - 1;
int it = -1;
while (left <= right) {
int mid = (left + right) / 2;
if (lis.get(mid) >= nums[i]) {
it = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
if (it == -1) {
lis.add(nums[i]);
} else {
lis.set(it, nums[i]);
}
}
return lis.size();
}
}
【動態規劃】coin-change(零錢兌換)
給定不同面額的硬幣 coins 和一個總金額 amount。編寫一個函數來計算可以湊成總金額所需的最少的硬幣個數。如果沒有任何一種硬幣組合能組成總金額,返回 -1
。
示例 1:
輸入: coins = [1, 2, 5], amount = 11
輸出: 3
解釋: 11 = 5 + 5 + 1
示例 2:
輸入: coins = [2], amount = 3
輸出: -1
說明:
你可以認爲每種硬幣的數量是無限的。
代碼
class Solution {
public int coinChange(int[] coins, int amount) {
int max = amount + 1;
int[] dp = new int[max];
Arrays.fill(dp, max);
dp[0] = 0;
for (int i = 1; i < max; i ++) {
for (int j = 0; j < coins.length; j ++) {
if (coins[j] <= i) {
dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
}
}
}
return dp[amount] > amount ? -1 : dp[amount];
}
}
【動態規劃】edit-distance(編輯距離)
給定兩個單詞 word1 和 word2,計算出將 word1 轉換成 word2 所使用的最少操作數 。
你可以對一個單詞進行如下三種操作:
- 插入一個字符
- 刪除一個字符
- 替換一個字符
示例 1:
輸入: word1 = "horse", word2 = "ros"
輸出: 3
解釋:
horse -> rorse (將 'h' 替換爲 'r')
rorse -> rose (刪除 'r')
rose -> ros (刪除 'e')
示例 2:
輸入: word1 = "intention", word2 = "execution"
輸出: 5
解釋:
intention -> inention (刪除 't')
inention -> enention (將 'i' 替換爲 'e')
enention -> exention (將 'n' 替換爲 'x')
exention -> exection (將 'n' 替換爲 'c')
exection -> execution (插入 'u')
代碼
class Solution {
public int minDistance(String word1, String word2) {
//dp[i][j] word1的前i個字符替換到word2的前j個字符最少需要的步數
int m = word1.length();
int n = word2.length();
int[][] dp = new int[m + 1][n + 1];
for (int i = 0; i < m + 1; i ++) dp[i][0] = i;//word2長度爲0 逐個刪除
for (int i = 0; i < n + 1; i ++) dp[0][i] = i;//word1長度爲0 逐個添加
for (int i = 1; i < m + 1; i ++) {
for (int j = 1; j < n + 1; j ++) {
if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1];
} else {
//dp[i - 1][j]代表word1插入一個字符
//dp[i][j - 1]代表word1刪除一個字符
//dp[i - 1][j - 1]代表word1替換一個字符
dp[i][j] = Math.min(Math.min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;
}
}
}
return dp[m][n];
}
}
【並查集】代碼模板
public class UnionFind {
/**
* 根數組 i = roots[j]代表j的根是i
* 規定根相同的元素屬於同一集合
*/
private int[] roots;
/**
* 並查集的深度(從0開始)
*/
private int[] rank;
/**
* 初始化根數組,每個元素的根爲自己
* @param N
*/
public UnionFind(int N) {
roots = new int[N];
for (int i = 0; i < N; i++) {
roots[i] = i;
}
}
/**
* 判斷兩個元素是否屬於同一個集合,即根元素是否相同
* @param p
* @param q
* @return
*/
public boolean connected(int p, int q) {
return findRoot(p) == findRoot(q);
}
/**
* 將兩個集合合併爲1個集合
* @param p
* @param q
*/
public void union(int p, int q) {
int qroot = findRoot(q);
int proot = findRoot(p);
if (qroot != proot) {
//優化並查集,減少並查集的深度
if (rank[proot] > rank[qroot]) {
roots[qroot] = proot;
} else if (rank[proot] < rank[qroot]) {
roots[proot] = qroot;
} else {
roots[proot] = qroot;
rank[qroot] += 1;
}
}
}
/**
* 尋找指定元素的根元素
* @param i
* @return
*/
private int findRoot(int i) {
int root = i;
while (root != roots[root]) {
root = roots[root];
}
//優化並查集,將元素直接指向根元素,壓縮路徑
while (i != roots[i]) {
int tmp = roots[i];
roots[i] = root;
i = tmp;
}
return root;
}
}
【並查集】number-of-islands(島嶼數量)
給定一個由 '1'
(陸地)和 '0'
(水)組成的的二維網格,計算島嶼的數量。一個島被水包圍,並且它是通過水平方向或垂直方向上相鄰的陸地連接而成的。你可以假設網格的四個邊均被水包圍。
示例 1:
輸入:
11110
11010
11000
00000
輸出: 1
示例 2:
輸入:
11000
11000
00100
00011
輸出: 3
代碼
DFS
class Solution {
int count = 0;
int[] dx = {-1, 1, 0, 0};
int[] dy = {0, 0, -1, 1};
public int numIslands(char[][] grid) {
for (int i = 0; i < grid.length; i ++) {
for (int j = 0; j < grid[0].length; j ++) {
if (grid[i][j] == '1') {
count ++;
dfs(grid, i, j);
}
}
}
return count;
}
private void dfs(char[][] grid, int i, int j) {
if (valid(grid, i, j)) {
grid[i][j] = '0';
for (int k = 0; k < 4; k ++) {
dfs(grid, i + dx[k], j + dy[k]);
}
}
}
private boolean valid(char[][] grid, int i, int j) {
return i >= 0 && i < grid.length && j >=0 && j < grid[0].length && grid[i][j] == '1';
}
}
並查集
class Solution {
public int numIslands(char[][] grid) {
if (grid == null || grid.length == 0) return 0;
UnionFind uf = new UnionFind(grid);
int[] dx = {-1, 1, 0, 0};
int[] dy = {0, 0, -1, 1};
int m = grid.length;
int n = grid[0].length;
for (int i = 0; i < m; i ++) {
for (int j = 0; j < n; j ++) {
if (grid[i][j] == '0') {
continue;
}
for (int k = 0; k < 4; k ++) {
int a = i + dx[k];
int b = j + dy[k];
if (valid(grid, a, b)) {
uf.union(i * n + j, a * n + b);
}
}
}
}
return uf.getCount();
}
private boolean valid(char[][] grid, int i, int j) {
return i >= 0 && i < grid.length && j >=0 && j < grid[0].length && grid[i][j] == '1';
}
}
class UnionFind {
private int count;
private int[] roots;
private int[] rank;
public UnionFind(char[][] grid) {
int m = grid.length;
int n = grid[0].length;
count = 0;
roots = new int[m * n];
rank = new int[m * n];
Arrays.fill(roots, -1);
for (int i = 0; i < m; i ++) {
for (int j = 0; j < n; j ++) {
if (grid[i][j] == '1') {
roots[i * n + j] = i * n + j;
count += 1;
}
}
}
}
public int getCount() {
return this.count;
}
public void union(int p, int q) {
int proot = find(p);
int qroot = find(q);
if (qroot != proot) {
if (rank[proot] > rank[qroot]) {
roots[qroot] = proot;
} else if (rank[proot] < rank[qroot]) {
roots[proot] = qroot;
} else {
roots[proot] = qroot;
rank[qroot] += 1;
}
count -= 1;
}
}
private int find(int i) {
int root = i;
while (root != roots[root]) {
root = roots[root];
}
while (i != roots[i]) {
int tmp = roots[i];
roots[i] = root;
i = tmp;
}
return root;
}
}
【並查集】friend-circles(朋友圈)
班上有 N 名學生。其中有些人是朋友,有些則不是。他們的友誼具有是傳遞性。如果已知 A 是 B 的朋友,B 是 C 的朋友,那麼我們可以認爲 A 也是 C 的朋友。所謂的朋友圈,是指所有朋友的集合。
給定一個 N * N 的矩陣 M,表示班級中學生之間的朋友關係。如果M[i][j] = 1,表示已知第 i 個和 j 個學生互爲朋友關係,否則爲不知道。你必須輸出所有學生中的已知的朋友圈總數。
示例 1:
輸入:
[[1,1,0],
[1,1,0],
[0,0,1]]
輸出: 2
說明:已知學生0和學生1互爲朋友,他們在一個朋友圈。
第2個學生自己在一個朋友圈。所以返回2。
示例 2:
輸入:
[[1,1,0],
[1,1,1],
[0,1,1]]
輸出: 1
說明:已知學生0和學生1互爲朋友,學生1和學生2互爲朋友,所以學生0和學生2也是朋友,所以他們三個在一個朋友圈,返回1。
注意:
- N 在[1,200]的範圍內。
- 對於所有學生,有
M[i][i] = 1
。 - 如果有
M[i][j] = 1
,則有M[j][i]= 1
。
代碼
DFS
class Solution {
int count = 0;
public int findCircleNum(int[][] M) {
int[] mark = new int[M.length];//用於存儲朋友圈路徑
for (int i = 0; i < M.length; i ++) {
if (mark[i] == 0) {
//0代表i沒有加入到一個朋友圈,從該點開始,dfs找出他的所有朋友
count ++;
dfs(M, mark, i);//執行一次,構建一條朋友圈
}
}
return count;
}
private void dfs(int[][] M, int[] mark, int i) {
mark[i] = 1;//將i放到朋友圈
for (int j = 0; j < M[0].length; j ++) {
if (M[i][j] == 1 && mark[j] == 0) {
//如果i和j是朋友,但是j沒有加入該朋友圈,dfs去構建朋友圈
dfs(M, mark, j);
}
}
}
}
並查集
class Solution {
public int findCircleNum(int[][] M) {
UnionFind uf = new UnionFind(M);
int m = M.length;
int n = M[0].length;
for (int i = 0; i < m; i ++) {
for (int j = 0; j < n; j ++) {
if (M[i][j] == 1) {
//這裏太暴力了,暫時沒有優化,速度那是慢的一匹
//上面的dfs是優化過的,換了個思路,當然也可以按照下面暴力求解
for (int k = 0; k < m; k ++) {
if (M[k][j] == 1) {
uf.union(i * n + j, k * n + j);
}
}
for (int k = 0; k < n; k ++) {
if (M[i][k] == 1) {
uf.union(i * n + j, i * n + k);
}
}
}
}
}
return uf.getCount();
}
}
class UnionFind {
private int count;
private int[] roots;
private int[] rank;
public UnionFind(int[][] M) {
int m = M.length;
int n = M[0].length;
count = 0;
roots = new int[m * n];
rank = new int[m * n];
Arrays.fill(roots, -1);
for (int i = 0; i < m; i ++) {
for (int j = 0; j < n; j ++) {
if (M[i][j] == 1) {
roots[i * n + j] = i * n + j;
count += 1;
}
}
}
}
public int getCount() {
return this.count;
}
public void union(int p, int q) {
int proot = find(p);
int qroot = find(q);
if (qroot != proot) {
if (rank[proot] > rank[qroot]) {
roots[qroot] = proot;
} else if (rank[proot] < rank[qroot]) {
roots[proot] = qroot;
} else {
roots[proot] = qroot;
rank[qroot] += 1;
}
count -= 1;
}
}
private int find(int i) {
int root = i;
while (root != roots[root]) {
root = roots[root];
}
while (i != roots[i]) {
int tmp = roots[i];
roots[i] = root;
i = tmp;
}
return root;
}
}
【LRU】lru-cache(LRU緩存機制)
運用你所掌握的數據結構,設計和實現一個 LRU (最近最少使用) 緩存機制。它應該支持以下操作: 獲取數據 get
和 寫入數據 put
。
獲取數據 get(key)
- 如果密鑰 (key) 存在於緩存中,則獲取密鑰的值(總是正數),否則返回 -1。
寫入數據 put(key, value)
- 如果密鑰不存在,則寫入其數據值。當緩存容量達到上限時,它應該在寫入新數據之前刪除最近最少使用的數據值,從而爲新的數據值留出空間。
進階:
你是否可以在 O(1) 時間複雜度內完成這兩種操作?
示例:
LRUCache cache = new LRUCache( 2 /* 緩存容量 */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 該操作會使得密鑰 2 作廢
cache.get(2); // 返回 -1 (未找到)
cache.put(4, 4); // 該操作會使得密鑰 1 作廢
cache.get(1); // 返回 -1 (未找到)
cache.get(3); // 返回 3
cache.get(4); // 返回 4
代碼
class LRUCache {
private LinkedHashMap<Integer, Integer> map;
private int remain;
public LRUCache(int capacity) {
this.map = new LinkedHashMap<>(capacity);
this.remain = capacity;
}
public int get(int key) {
if (!map.containsKey(key)) return -1;
int v = map.remove(key);
map.put(key, v);
return v;
}
public void put(int key, int value) {
if (map.containsKey(key)) {
map.remove(key);
} else {
if (this.remain > 0) this.remain --;
else removeOldestEntry();
}
map.put(key, value);
}
private void removeOldestEntry() {
Iterator<Integer> it = this.map.keySet().iterator();
int oldestKey = -1;
if (it.hasNext()) {
oldestKey = it.next();
}
map.remove(oldestKey);
}
}
/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache obj = new LRUCache(capacity);
* int param_1 = obj.get(key);
* obj.put(key,value);
*/