字符串
力扣3 无重复字符的最长子串
https://leetcode.cn/problems/longest-substring-without-repeating-characters/
思路:
滑动窗口法。
值得掌握的API:
Java处理字符串不方便,常用str.toCharArray()转化成字符数组,或者用str.charAt(int index)来找对应下标上的字符。
class Solution {
public int lengthOfLongestSubstring(String s) {
char[] sArr = s.toCharArray();
Map<Character, Integer> window = new HashMap();
int p1 = 0;
int p2 = 0;
int maxWindow = 0;
while(p2 < sArr.length){
char c = sArr[p2];
p2++;
window.put(c, window.getOrDefault(c, 0) + 1);
while(window.get(c) > 1){
char d = sArr[p1];
p1++;
window.put(d, window.get(d) - 1);
}
maxWindow = Math.max(maxWindow, p2 - p1);
}
return maxWindow;
}
}
力扣49 字母异位词分组
https://leetcode.cn/problems/group-anagrams/
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
Map<String, List<String>> memo = new HashMap();
for(String str : strs){
char[] charArr = str.toCharArray();
Arrays.sort(charArr);
String key = new String(charArr);
List<String> list = memo.getOrDefault(key, new ArrayList());
list.add(str);
memo.put(key, list);
}
List<List<String>> res = new ArrayList();
for(String k : memo.keySet()){
res.add(memo.get(k));
}
return res;
}
}
力扣30 串联所有单词的子串
https://leetcode.cn/problems/substring-with-concatenation-of-all-words/
思路:
没啥思路,用的暴力解法。
轻易不要用回溯法,时间复杂度O(n!),动不动就超时。
值得掌握的API:
str.substring(int begin, int end) 包头不包尾。
class Solution {
public List<Integer> findSubstring(String s, String[] words) {
int sl = s.length();
int m = words.length;
int n = words[0].length();
int wl = m * n;
if(wl > sl){
return new LinkedList();
}
List<Integer> res = new LinkedList();
int p1 = 0;
int p2 = wl;
while(p2 <= sl) {
// 截取字符串, 判断是否是串联子串
if(work(words, s.substring(p1, p2))){
res.add(p1);
}
p1++;
p2++;
}
return res;
}
public boolean work(String[] words, String str){
int n = words[0].length();
Map<String, Integer> dirc = new HashMap();
for(int i = 0; i <= str.length() - n; i = i + n){
String s = str.substring(i, i + n);
dirc.put(s, dirc.getOrDefault(s, 0) + 1);
}
for(String w : words){
if(!dirc.containsKey(w)){
return false;
}
if(dirc.get(w) - 1 < 0){
return false;
}
dirc.put(w, dirc.get(w) - 1);
}
return true;
}
}
链表
力扣86 分隔链表
https://leetcode.cn/problems/partition-list/
思路:
创建两个链表,一个链表放小于x的结点,另一个放大于等于x的结点。每次从原链表上把头结点取下,与x作比较,决定放在哪个链表后面。最后把两个链表相连。
class Solution {
public ListNode partition(ListNode head, int x) {
ListNode dummy1 = new ListNode(0);
ListNode dummy2 = new ListNode(0);
ListNode p1 = dummy1;
ListNode p2 = dummy2;
ListNode next = head;
while(head != null){
next = head.next;
head.next = null;
if(head.val < x){
p1.next = head;
p1 = p1.next;
} else{
p2.next = head;
p2 = p2.next;
}
head = next;
}
p1.next = dummy2.next;
return dummy1.next;
}
}
力扣16 最接近的三数之和
https://leetcode.cn/problems/3sum-closest/
思路:
先定下第一个数,然后使用对撞指针的方法求和。
class Solution {
public int threeSumClosest(int[] nums, int target) {
Arrays.sort(nums);
int n = nums.length;
int minsub = Integer.MAX_VALUE;
Map<Integer, Integer> memo = new HashMap();
for(int i = 0; i < n - 2; i++){
int p1 = i + 1;
int p2 = n - 1;
while(p1 < p2){
int sum = nums[i] + nums[p1] + nums[p2];
if(sum == target){
return target;
}
if(sum > target){
p2--;
} else{
p1++;
}
int sub = Math.abs(sum - target);
if(sub < minsub){
minsub = sub;
memo.put(minsub, sum);
}
}
}
return memo.get(minsub);
}
}
力扣27 移除元素
https://leetcode.cn/problems/remove-element/
思路:
使用对撞双指针。遍历数组,遇到等于val的值时,与从后往前数第一个不等于val的值交换位置。
class Solution {
public int removeElement(int[] nums, int val) {
int n = nums.length;
int p1 = 0;
int p2 = n - 1;
// p2初始值n - 1, 考虑到极限情况下p1取到n - 1, p1应小于等于p2而不是小于
while(p1 <= p2){
if(nums[p2] == val){
p2--;
continue;
}
if(nums[p1] == val){
swap(nums, p1, p2);
}
p1++;
}
return p1;
}
public void swap(int[] nums, int a, int b){
int temp = nums[a];
nums[a] = nums[b];
nums[b] = temp;
}
}
力扣732 我的日程安排表III
https://leetcode.cn/problems/my-calendar-iii/
思路:
一眼差分数组,只是日程区间的取值范围较大,直接用数组会内存溢出,所以用treeMap来实现一个逻辑上的差分数组。
值得掌握的API:
for(Map.Entry<xx, xx> entry : memo.entrySet()){ }、entry.getKey()、entry.getValue(),使用entrySet比使用keySet遍历要快一些。
class MyCalendarThree {
Map<Integer, Integer> memo;
public MyCalendarThree() {
this.memo = new TreeMap();
}
public int book(int startTime, int endTime) {
memo.put(startTime, memo.getOrDefault(startTime, 0) + 1);
memo.put(endTime, memo.getOrDefault(endTime, 0) - 1);
int k = 0;
int sum = 0;
for(Map.Entry<Integer, Integer> entry : memo.entrySet()){
sum += entry.getValue();
k = Math.max(k, sum);
}
return k;
}
}
队列
力扣641 设计循环双端队列
https://leetcode.cn/problems/design-circular-deque/
思路:
使用一个数组实现双端队列。
使用两个指针head、tail,分别称为头指针和尾指针,head指向队列中存储的第一个元素,tail指向存储的最后一个元素。队列中未存储任何元素时,tail在head的前一位。
向队列头部插入新元素时,头指针前移,从队列头部删除元素时,头指针后移。向队列尾部插入新元素时,尾指针后移,从队列尾部删除元素时,尾指针前移。
若头指针或尾指针前移后越界,则指向数组的最大下标处,后移后越界,则指向数组的0坐标处。
维护一个自然数count,记录当前队列中存储元素的数目。count等于0时,不允许做删除操作,count等于数组大小时,不允许做插入操作。
class MyCircularDeque {
int[] arr;
int head;
int tail;
int count;
public MyCircularDeque(int k) {
this.arr = new int[k];
this.head = 0;
this.tail = arr.length - 1;
this.count = 0;
}
public boolean insertFront(int value) {
if(count == arr.length){
return false;
}
head--;
if(head == -1){
head = arr.length - 1;
}
arr[head] = value;
count++;
return true;
}
public boolean insertLast(int value) {
if(count == arr.length){
return false;
}
tail++;
if(tail == arr.length){
tail = 0;
}
arr[tail] = value;
count++;
return true;
}
public boolean deleteFront() {
if(count == 0){
return false;
}
head++;
if(head == arr.length){
head = 0;
}
count--;
return true;
}
public boolean deleteLast() {
if(count == 0){
return false;
}
tail--;
if(tail == -1){
tail = arr.length - 1;
}
count--;
return true;
}
public int getFront() {
if(count == 0){
return -1;
}
return arr[head];
}
public int getRear() {
if(count == 0){
return -1;
}
return arr[tail];
}
public boolean isEmpty() {
return count == 0;
}
public boolean isFull() {
return count == arr.length;
}
}
力扣406 根据身高重建队列
https://leetcode.cn/problems/queue-reconstruction-by-height/
思路:
由于身高较矮的人不会影响身高较高的人的排名,所以将people按身高升序排列,身高相等时按k值降序排列(下面会解释)。
创建一个返回数组,开始向数组中插空:遍历排序后的people,总是将people[a][b]插到当前第(b+1)个空上。两人同身高的情况下先插k值较大的人,因为这样不会影响k值较小的人排在第几个空位,反过来会有影响。
class Solution {
public int[][] reconstructQueue(int[][] people) {
int n = people.length;
Arrays.sort(people, (a, b) -> {
if(a[0] == b[0]){
return b[1] - a[1];
}
return a[0] - b[0];
});
int[][] res = new int[n][2];
boolean[] used = new boolean[n];
for(int i = 0; i < n; i++){
int count = 0;
for(int j = 0; j < n; j++){
if(used[j]){
continue;
}
if(people[i][1] == count){
res[j][0] = people[i][0];
res[j][1] = people[i][1];
used[j] = true;
break;
}
count++;
}
}
return res;
}
}
力扣899 有序队列
https://leetcode.cn/problems/orderly-queue/
思路:
当k>=2时,字符串可以以任何顺序返回,经典厄斐琉斯切枪问题。
值得掌握的API:
字符串构造器相关:new StringBuilder(String str)、sb.charAt(int index)、sb.deleteCharAt(int index)。
字符串字典顺序比较:str.compareTo(String str)。
class Solution {
public String orderlyQueue(String s, int k) {
if(k > 1){
char[] sArr = s.toCharArray();
Arrays.sort(sArr);
return new String(sArr);
} else if(k == 1){
String res = s;
StringBuilder sb = new StringBuilder(s);
int n = s.length();
for(int i = 0; i < n; i++){
sb.append(sb.charAt(0));
sb.deleteCharAt(0);
String str = sb.toString();
if(str.compareTo(res) < 0){
res = str;
}
}
return res;
} else{
return null;
}
}
}
栈
力扣946 验证栈序列
https://leetcode.cn/problems/validate-stack-sequences/
思路:
遍历popped,验证其中每一个元素。
class Solution {
public boolean validateStackSequences(int[] pushed, int[] popped) {
Stack<Integer> stack = new Stack();
int n = pushed.length;
int index = 0;
for(int p : popped){
if(!stack.isEmpty() && p == stack.peek()){
stack.pop();
continue;
}
while(index < n && p != pushed[index]){
stack.push(pushed[index]);
index++;
}
if(index == n){
break;
}
index++;
}
return stack.isEmpty();
}
}
力扣116 填充每个结点的下一个右侧结点指针
https://leetcode.cn/problems/populating-next-right-pointers-in-each-node/
思路:
铁索连环。
class Solution {
public Node connect(Node root) {
if(root == null){
return null;
}
traverse(root.left, root.right);
return root;
}
public void traverse(Node left, Node right){
if(left == null || right == null){
return;
}
left.next = right;
traverse(left.left, left.right);
traverse(left.right, right.left);
traverse(right.left, right.right);
}
}
力扣117 填充每个结点的下一个右侧结点指针II
https://leetcode.cn/problems/populating-next-right-pointers-in-each-node-ii/
值得掌握的API:
queue.offer(Object o)、queue.poll()。
小技巧:
在BFS算法的while(!queue.isEmpty())循环体中,获取当前队列大小并根据队列大小进行for循环,可以保证外层循环每次处理二叉树同一高度上的所有结点。
class Solution {
Queue<Node> queue = new LinkedList();
public Node connect(Node root) {
queue.offer(root);
while(!queue.isEmpty()){
int size = queue.size();
Node pre = null;
for(int i = 0; i < size; i++){
Node current = queue.poll();
if(current == null){
continue;
}
queue.offer(current.left);
queue.offer(current.right);
if(pre != null){
pre.next = current;
}
pre = current;
}
}
return root;
}
}
力扣895 最大频率栈
https://leetcode.cn/problems/maximum-frequency-stack/
值得掌握的API:
hashMap.putIfAbsent(Object key, Object value)。
class FreqStack {
// 元素->次数
Map<Integer, Integer> keytimes;
// 次数->元素
Map<Integer, Stack<Integer>> timeskeys;
// 最大次数
int maxTimes;
public FreqStack() {
this.keytimes = new HashMap();
this.timeskeys = new HashMap();
this.maxTimes = 0;
}
public void push(int val) {
int times = keytimes.getOrDefault(val, 0) + 1;
keytimes.put(val, times);
// putIfAbsent: 向hashmap中存储键值对时先检查键是否已存在, 不存在才会put
// 错误记录: Stack<Integer> stack = timeskeys.getOrDefault(times, new Stack());
timeskeys.putIfAbsent(times, new Stack());
Stack<Integer> stack = timeskeys.get(times);
if(stack.isEmpty()){
maxTimes++;
}
stack.push(val);
}
public int pop() {
Stack<Integer> stack = timeskeys.get(maxTimes);
int res = stack.pop();
if(stack.isEmpty()){
maxTimes--;
}
keytimes.put(res, keytimes.get(res) - 1);
return res;
}
}
哈希表
力扣61 旋转链表
https://leetcode.cn/problems/rotate-list/
思路:
链表的断开重连,唯一需要注意的点是k可能大于链表总长度。
class Solution {
public ListNode rotateRight(ListNode head, int k) {
if(head == null){
return null;
}
// 计算实际右移步数
ListNode countNode = head;
int count = 0;
while(countNode != null){
count++;
countNode = countNode.next;
}
int step = k % count;
// 双指针, p2先行step步
ListNode p1 = head;
ListNode p2 = head;
for(int i = 0; i < step; i++){
p2 = p2.next;
}
// 双指针前进, 直到p2指向末结点, 此时p1.next是旋转后的头结点
while(p2.next != null){
p1 = p1.next;
p2 = p2.next;
}
// 获得旋转后链表
p2.next = head;
head = p1.next;
p1.next = null;
return head;
}
}
力扣729 我的日程安排表I
https://leetcode.cn/problems/my-calendar-i/
class MyCalendar {
List<int[]> booked;
public MyCalendar() {
this.booked = new ArrayList();
}
public boolean book(int start, int end) {
for (int[] arr : booked) {
int left = arr[0], right = arr[1];
if (start < right && end > left) {
return false;
}
}
booked.add(new int[]{start, end});
return true;
}
}
力扣25 K个一组翻转链表
https://leetcode.cn/problems/reverse-nodes-in-k-group/
思路:
使用双指针,分隔出大小为k的窗口,将窗口内的部分断开、翻转、重连,然后滑动窗口。
class Solution {
// 思路: 把要翻转的部分断开, 翻转, 重连
public ListNode reverseKGroup(ListNode head, int k) {
ListNode dummy = new ListNode(0, head);
ListNode p1 = dummy;
ListNode p2 = dummy;
// p2先行k步, 形成大小为k的窗口
for(int i = 0; i < k; i++){
p2 = p2.next;
}
// k个一组翻转链表
while(p2 != null){
ListNode p3 = p1.next;
ListNode p4 = p2.next;
// 断开
p1.next = null;
p2.next = null;
// 翻转
p1.next = reverse(p3);
// 重连
p2 = p1;
while(p2.next != null){
p2 = p2.next;
}
p2.next = p4;
// 准备下一轮翻转
for(int i = 0; i < k; i++){
p2 = p2.next;
if(p2 == null){
break;
}
p1 = p1.next;
}
}
return dummy.next;
}
/**
* 翻转链表
*/
public ListNode reverse(ListNode head){
// 错误记录: 未考虑到head.next == null的情况
if(head == null || head.next == null){
return head;
}
ListNode res = reverse(head.next);
head.next.next = head;
head.next = null;
return res;
}
}
力扣554 砖墙
https://leetcode.cn/problems/brick-wall/
思路:
找到最大的缝隙高度,用墙体高度与最大缝隙高度相减。
class Solution {
public int leastBricks(List<List<Integer>> wall) {
// 位置->缝隙高度
Map<Integer, Integer> indexTimes = new HashMap();
// 最大缝隙高度
int maxIndex = 0;
// 墙体高度
int heigth = 0;
for(List<Integer> row : wall){
int nums = row.size();
int sum = 0;
for(int i = 0; i < nums - 1; i++){
sum += row.get(i);
indexTimes.put(sum, indexTimes.getOrDefault(sum, 0) + 1);
if(indexTimes.get(sum) > maxIndex){
maxIndex = indexTimes.get(sum);
}
}
heigth++;
}
return heigth - maxIndex;
}
}
DFS
力扣105 从前序与中序遍历序列构造二叉树
https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/
思路:
使用一个hashMap记录inorder数组上各值的下标。
class Solution {
Map<Integer, Integer> valToIndexMap = new HashMap();
public TreeNode buildTree(int[] preorder, int[] inorder) {
for (int i = 0; i < inorder.length; i++) {
valToIndexMap.put(inorder[i], i);
}
return buildTree(preorder, inorder, 0, preorder.length - 1, 0, inorder.length - 1);
}
public TreeNode buildTree(int[] preorder, int[] inorder, int pl, int pr, int il, int ir){
if(pl > pr){
return null;
}
int temp = preorder[pl];
int mid = valToIndexMap.get(temp);
TreeNode res = new TreeNode(temp);
res.left = buildTree(preorder, inorder, pl + 1, pl + (mid - il), il, mid - 1);
res.right = buildTree(preorder, inorder, pl + (mid - il) + 1, pr, mid + 1, ir);
return res;
}
}
力扣112 路径总和
https://leetcode.cn/problems/path-sum/
思路:
结果等价于左子树或右子树上是否存在一条路径的路径和等于targetSum - root.val。
class Solution {
public boolean hasPathSum(TreeNode root, int targetSum) {
if(root == null){
return false;
}
return dps(root.left, root.right, targetSum - root.val);
}
public boolean dps(TreeNode left, TreeNode right, int targetSum){
if(left == null && right == null){
return targetSum == 0;
}
boolean res = false;
if(left != null){
res = res || dps(left.left, left.right, targetSum - left.val);
}
if(right != null){
res = res || dps(right.left, right.right, targetSum - right.val);
}
return res;
}
}
力扣98 验证二叉搜索树
https://leetcode.cn/problems/validate-binary-search-tree/
class Solution {
public boolean isValidBST(TreeNode root) {
return isValidBST(root, null, null);
}
public boolean isValidBST(TreeNode root, TreeNode min, TreeNode max){
if(root == null){
return true;
}
if(min != null && root.val <= min.val){
return false;
}
if(max != null && root.val >= max.val){
return false;
}
// 错误记录: 未传递min和max
return isValidBST(root.left, min, root) && isValidBST(root.right, root, max);
}
}
力扣494 目标和
https://leetcode.cn/problems/target-sum/
思路:
解题关键在于,将整数数组分为加正号的一组和加负号的一组。
sum(正) - sum(负) = target
sum(正) + sum(负) = sum
两方程相加得到:sum(正) = (target + sum) / 2,问题转化成 0-1背包问题。
class Solution {
public int findTargetSumWays(int[] nums, int target) {
// 转化成背包问题
int sum = 0;
for(int i : nums){
sum += i;
}
if(sum < Math.abs(target) || (sum + target) % 2 != 0){
return 0;
}
int bag = (target + sum) / 2;
int n = nums.length;
// 1. dp[i][j]的含义: 使用nums的前i个数, 正好装满容积为j的背包, 可能的选择方式的数目
// 需要注意的是在转化成背包问题后, 数值用或不用的取值为i或0, 而非i或-i
int[][] dp = new int[n + 1][bag + 1];
// 2. base case: 使用0个数, 使j等于0, 算一种情况
// 隐含的base case: 使用0个数, 使1 <= j <= bag, dp[0][j]为0
dp[0][0] = 1;
for(int i = 1; i <= n; i++){
for(int j = 0; j <= bag; j++){
// 第i个数, 比j大, 一定装不了(取值0)
if(j < nums[i - 1]){
dp[i][j] = dp[i - 1][j];
} else{
// 第i个数, 大于等于j, 可以装(取值i)也可以不装(取值0)
dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i - 1]];
}
}
}
return dp[n][bag];
}
}
力扣547 省份数量
https://leetcode.cn/problems/number-of-provinces/
思路:
一维的DFS。
class Solution {
public int findCircleNum(int[][] isConnected) {
int count = 0;
int n = isConnected.length;
boolean[] visited = new boolean[n];
for(int i = 0; i < n; i++){
if(!visited[i]){
count++;
dfs(isConnected, visited, i);
}
}
return count;
}
public void dfs(int[][] isConnected, boolean[] visited, int i){
int n = isConnected.length;
for(int j = 0; j < n; j++){
if(isConnected[i][j] == 1 && !visited[j]){
visited[j] = true;
dfs(isConnected, visited, j);
}
}
}
}
力扣1254 统计封闭岛屿的数目
https://leetcode.cn/problems/number-of-closed-islands/
思路:
先沿岸搜索半岛,再搜索封闭岛屿。
class Solution {
public int closedIsland(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
// 沿岸搜索半岛
for(int i = 0; i < m; i++){
if(grid[i][0] == 0){
dfs(grid, i, 0);
}
if(grid[i][n - 1] == 0){
dfs(grid, i, n - 1);
}
}
for(int j = 0; j < n; j++){
if(grid[0][j] == 0){
dfs(grid, 0, j);
}
if(grid[m - 1][j] == 0){
dfs(grid, m - 1, j);
}
}
// 统计封闭岛屿
int count = 0;
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
if(grid[i][j] == 0){
count++;
dfs(grid, i, j);
}
}
}
return count;
}
public void dfs(int[][] grid, int i, int j){
int m = grid.length;
int n = grid[0].length;
if(i < 0 || i >= m || j < 0 || j >= n){
return;
}
if(grid[i][j] == 1){
return;
}
grid[i][j] = 1;
dfs(grid, i - 1, j);
dfs(grid, i, j - 1);
dfs(grid, i + 1, j);
dfs(grid, i, j + 1);
}
}
BFS
力扣1091 二进制矩阵中的最短路径
https://leetcode.cn/problems/shortest-path-in-binary-matrix/
思路:
求最短路径很容易想到BFS。
class Solution {
public int shortestPathBinaryMatrix(int[][] grid) {
int n = grid.length;
return bfs(grid, 0, 0, n - 1, n - 1);
}
public int bfs(int[][] grid, int starti, int startj, int endi, int endj){
int n = grid.length;
// bfs核心数据结构: 队列
Queue<int[]> queue = new LinkedList();
// 记录经过的坐标
boolean[][] visited = new boolean[n][n];
// 录入起点
queue.offer(new int[]{starti, startj});
int stepCount = 0;
// 广度优先遍历
while(!queue.isEmpty()){
stepCount++;
int sz = queue.size();
for(int k = 0; k < sz; k++){
int[] arr = queue.poll();
int i = arr[0];
int j = arr[1];
if(i < 0 || i >= n || j < 0 || j >= n){
continue;
}
if(visited[i][j]){
continue;
}
if(grid[i][j] != 0){
continue;
}
if(i == endi && j == endj){
return stepCount;
}
queue.offer(new int[]{i - 1, j - 1});
queue.offer(new int[]{i, j - 1});
queue.offer(new int[]{i + 1, j - 1});
queue.offer(new int[]{i - 1, j});
queue.offer(new int[]{i + 1, j});
queue.offer(new int[]{i - 1, j + 1});
queue.offer(new int[]{i, j + 1});
queue.offer(new int[]{i + 1, j + 1});
visited[i][j] = true;
}
}
return -1;
}
}
力扣1129 颜色交替的最短路径
https://leetcode.cn/problems/shortest-path-with-alternating-colors/
思路:
BFS。
把红色边和蓝色边分别存到两个hashMap中,方便取下一步的可选项。
题目中没有要求第一步要走红色边还是蓝色边,所以要分别考虑这两种情况。
做完每一步的遍历后,也要调整下一步可选择的边的颜色。
class Solution {
public int[] shortestAlternatingPaths(int n, int[][] redEdges, int[][] blueEdges) {
// 把redEdges和blueEdges转化成映射表
Map<Integer, List<Integer>> redMap = getMap(redEdges);
Map<Integer, List<Integer>> blueMap = getMap(blueEdges);
int[] res = new int[n];
for(int i = 0; i < n; i++){
// 先走红色边
int res1 = bfs(n, redMap, blueMap, 0, i, true);
// 先走蓝色边
int res2 = bfs(n, redMap, blueMap, 0, i, false);
if(res1 != -1 && res2 != -1){
res[i] = Math.min(res1, res2);
} else{
res[i] = Math.max(res1, res2);
}
}
return res;
}
public int bfs(int n, Map<Integer, List<Integer>> redMap, Map<Integer, List<Integer>> blueMap, int start, int end, boolean startRed){
// bfs核心数据结构: 队列
Queue<Integer> queue = new LinkedList();
// 经过的数字, 从红色边经过或从蓝色边经过分别记录
boolean[][] visited = new boolean[n][2];
// 开始是否选择红色边
boolean isRed = startRed;
// 起点, 在本题中start总为0, 这里把start写成变量是为了使方法更通用
queue.offer(start);
// 走过的步数
int stepCount = 0;
while(!queue.isEmpty()){
int sz = queue.size();
for(int num = 0; num < sz; num++){
int current = queue.poll();
if(visited[current][isRed ? 0 : 1]){
continue;
}
if(current == end){
return stepCount;
}
visited[current][isRed ? 0 : 1] = true;
List<Integer> options;
if(isRed){
options = redMap.get(current);
} else{
options = blueMap.get(current);
}
if(options == null){
continue;
}
for(int option : options){
queue.offer(option);
}
}
stepCount++;
isRed = !isRed;
}
return -1;
}
public Map<Integer, List<Integer>> getMap(int[][] edges){
Map<Integer, List<Integer>> resMap = new HashMap();
for(int[] arr : edges){
int key = arr[0];
int value = arr[1];
resMap.putIfAbsent(key, new LinkedList());
List<Integer> valueList = resMap.get(key);
valueList.add(value);
resMap.put(key, valueList);
}
return resMap;
}
}
力扣102 二叉树的层序遍历
https://leetcode.cn/problems/binary-tree-level-order-traversal/
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res = new LinkedList();
bfs(root, res);
return res;
}
public void bfs(TreeNode root, List<List<Integer>> res){
if(root == null){
return;
}
Queue<TreeNode> queue = new LinkedList();
queue.offer(root);
while(!queue.isEmpty()){
int sz = queue.size();
List<Integer> toollist = new LinkedList();
for(int i = 0; i < sz; i++){
TreeNode current = queue.poll();
toollist.add(current.val);
if(current.left != null){
queue.offer(current.left);
}
if(current.right != null){
queue.offer(current.right);
}
}
res.add(toollist);
}
}
}
力扣101 对称二叉树
https://leetcode.cn/problems/symmetric-tree/
思路:
迭代也好,递归也好,放两个根结点进队列,一个从左往右遍历,一个从右往左遍历。
class Solution {
public boolean isSymmetric(TreeNode root) {
Queue<TreeNode> queue = new LinkedList();
queue.offer(root);
queue.offer(root);
while(!queue.isEmpty()){
int sz = queue.size() / 2;
for(int i = 0; i < sz; i++){
TreeNode u = queue.poll();
TreeNode v = queue.poll();
if(u == null && v == null){
continue;
}
if(u == null || v == null || u.val != v.val){
return false;
}
queue.offer(u.left);
queue.offer(v.right);
queue.offer(u.right);
queue.offer(v.left);
}
}
return true;
}
}
力扣752 打开转盘锁
https://leetcode.cn/problems/open-the-lock/
思路:
求最小旋转次数,BFS。
值得掌握的API:
队列中可以放String,也可以放int[],在放String的时候遇到了一个问题是,Integer.valueOf('0')将字符‘0’转化成了48(ASCII码),正确的将char转化成int的API是Character.getNumericValue(char c),如果不记得这个API可以先把char转化成String(String.valueOf(char c)),再转化成int(Integer.parseInt(String str))。
class Solution {
public int openLock(String[] deadends, String target) {
Queue<int[]> queue = new LinkedList();
Set<String> memo = new HashSet();
for(String dead : deadends){
memo.add(dead);
}
queue.offer(new int[]{0, 0, 0, 0});
int stepCount = 0;
while(!queue.isEmpty()){
int sz = queue.size();
for(int j = 0; j < sz; j++){
int[] current = queue.poll();
int o1 = current[0];
int o2 = current[1];
int o3 = current[2];
int o4 = current[3];
String currentStr = "" + o1 + o2 + o3 + o4;
if(target.equals(currentStr)){
return stepCount;
}
if(memo.contains(currentStr)){
continue;
}
memo.add(currentStr);
queue.offer(new int[]{o1 == 0 ? 9 : o1 - 1, o2, o3, o4});
queue.offer(new int[]{o1 == 9 ? 0 : o1 + 1, o2, o3, o4});
queue.offer(new int[]{o1, o2 == 0 ? 9 : o2 - 1, o3, o4});
queue.offer(new int[]{o1, o2 == 9 ? 0 : o2 + 1, o3, o4});
queue.offer(new int[]{o1, o2, o3 == 0 ? 9 : o3 - 1, o4});
queue.offer(new int[]{o1, o2, o3 == 9 ? 0 : o3 + 1, o4});
queue.offer(new int[]{o1, o2, o3, o4 == 0 ? 9 : o4 - 1});
queue.offer(new int[]{o1, o2, o3, o4 == 9 ? 0 : o4 + 1});
}
stepCount++;
}
return -1;
}
}