本文部分方法图片来源于互联网上他人,这里纯属个人总结学习用,也希望能帮到其他小伙伴。
第 1 天 栈与队列(简单)
剑指 Offer 09. 用两个栈实现队列
- 定义s1、s2两个栈来完成此任务
- 在添加新元素阶段,直接添加即可
- 在删除元素阶段,先判断s1是否为空,若为空,返回-1
- 若不为空,则将s1逆置压入s2中
- 将s2的顶元素退出
- 再将s2元素逆置压入s1中
class CQueue
{
Stack<Integer> s1;
Stack<Integer> s2;
public CQueue()
{
s1 = new Stack<Integer>();
s2 = new Stack<Integer>();
}
public void appendTail(int value)
{
s1.push(value);
}
public int deleteHead()
{
if(s1.isEmpty())
{
return -1;
}
else
{
while (s1.isEmpty()==false)
{
s2.push(s1.pop());
}
}
int res = s2.pop();
while (s2.isEmpty()==false)
{
s1.push(s2.pop());
}
return res;
}
}
剑指 Offer 30. 包含min函数的栈
- 利用辅助栈来解决此问题
- s1中保存所有元素,s2中按条件压入元素
- 若s2为空,则压入元素。或者s2中的栈顶元素,大于待压入新元素,则压入新元素。保证s2中始终为递降序列。
- 当返回min时,返回s2元素的栈顶元素。此时只返回栈顶值,而不退出栈顶元素。
- java中stack类的peek()方法,实现此功能,只返回栈顶值,而不删除栈顶元素
- 当pop时,要将s1的栈顶退出。同时也要判断,s1退出的栈顶元素,是否和s2目前的栈顶元素相同,若相同则退出s2栈顶。避免出现s2有多余元素。
class MinStack
{
private Stack<Integer> s1;
private Stack<Integer> s2;
/** initialize your data structure here. */
public MinStack()
{
s1 = new Stack<>();
s2 = new Stack<>();
}
public void push(int x)
{
s1.push(x);
if(s2.empty()||s2.peek()>=x)
{
s2.push(x);
}
}
public void pop()
{
if(s1.pop().equals(s2.peek()))
s2.pop();
}
public int top()
{
return s1.peek();
}
public int min()
{
return s2.peek();
}
}
第 2 天 链表(简单)
剑指 Offer 06. 从尾到头打印链表
- Collections.reverse(res);java中对list逆置的方法
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public int[] reversePrint(ListNode head) {
if(head==null)
return new int[0];
List<Integer> res = new ArrayList<>();
while (head!=null)
{
res.add(head.val);
head = head.next;
}
Collections.reverse(res);
int [] ans = new int[res.size()];
for(int i =0;i<res.size();i++)
{
ans[i] = res.get(i);
}
return ans;
}
}
剑指 Offer 24. 反转链表
method1 辅助栈
- 使用辅助栈,来存储链表中节点元素值
- 再依次pop栈顶元素,替换对应节点的元素值
class Solution
{
public ListNode reverseList(ListNode head)
{
if(head == null)
return null;
Stack<Integer> map = new Stack<>();
ListNode temp = head;
while (temp!=null)
{
map.add(temp.val);
temp = temp.next;
}
ListNode cur = head;
while (cur!=null)
{
cur.val = map.pop();
cur = cur.next;
}
return head;
}
}
method2 迭代
- 定义两个指针,使用迭代的方法,依次调整节点的前后指向
class Solution
{
public ListNode reverseList(ListNode head)
{
ListNode pre = null;
ListNode cur = head;
while (cur!=null)
{
ListNode next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
}
method3 递归
class Solution
{
public ListNode reverseList(ListNode head)
{
if(head == null||head.next==null)
return head;
ListNode newhead = reverseList(head.next);
head.next.next = head;
head.next = null;
return head;
}
}
剑指 Offer 35. 复杂链表的复制
- 哈希表解决此问题
- 难点在于map.put(cur,new Node(cur.val));,key值不但是Node,value也是新的节点
- map.get(cur).next = map.get(cur.next)中, map.get(cur.next)获取的是cur.next的对应节点,实际是map中key值为cur.next对应的value值,然后让map.get(cur).next(这里对应的是key为cur的value值)为前面提到的value值
/*
// Definition for a Node.
class Node {
int val;
Node next;
Node random;
public Node(int val) {
this.val = val;
this.next = null;
this.random = null;
}
}
*/
class Solution {
public Node copyRandomList(Node head)
{
if(head==null)
return null;
//定义新指针,指向头结点
Node cur = head;
//定义哈希表
Map<Node,Node> map = new HashMap<>();
//从头到尾遍历,将每个节点,压入哈希表中,第一个值为指向该节点的指针,第二个值是该节点的值
while (cur!=null)
{
map.put(cur,new Node(cur.val));
cur = cur.next;
}
cur = head;
//从头到位遍历
while (cur!=null)
{
//map.get(cur.next)获得的是一个节点,然后将该节点赋给map.get(cur).next
map.get(cur).next = map.get(cur.next);
map.get(cur).random = map.get(cur.random);
cur = cur.next;
}
return map.get(head);
}
}
第 3 天 字符串(简单)
剑指 Offer 05. 替换空格
class Solution {
public String replaceSpace(String s) {
char [] chars = s.toCharArray();
String str = "";
for(char c:chars)
{
if(c==' ')
str=str+"%20";
else
{
str = str+c;
}
}
return str;
}
}
剑指 Offer 58 - II. 左旋转字符串
class Solution
{
public String reverseLeftWords(String s, int n)
{
String str = "";
char [] chars = s.toCharArray();
for(int i = n;i<s.length();i++)
{
str+=chars[i];
}
for(int i =0;i<n;i++)
{
str+=chars[i];
}
return str;
}
}
第 4 天 查找算法(简单)
剑指 Offer 03. 数组中重复的数字
method1
- 使用哈希表来解决此问题
class Solution {
public int findRepeatNumber(int[] nums) {
Map<Integer,Integer> map = new HashMap<>();
for(int i:nums)
{
if(map.containsKey(i))
return i;
else
map.put(i,1);
}
return -1;
}
}
method2
- 原地交换思想
class Solution {
public int findRepeatNumber(int[] nums) {
//使用原地交换的思想,解决此问题
int i = 0;
while (i<nums.length)
{
//若第i位置的值,为i本身,则continue
if(nums[i] == i)
{
i++;
continue;
}
//若nums[nums[i]] == nums[i]成立,意味着除过i位置之外,在其他位置还有元素值等于nums[i]
if(nums[nums[i]] == nums[i]) return nums[i];
//若上面条件不成立
int temp = nums[i];
nums[i] = nums[temp];
nums[temp] = temp;
}
return -1;
}
}
剑指 Offer 53 - I. 在排序数组中查找数字 I
- 不错,这个题是我自己想出来的办法,继续加油
//首先判断数组长度,若过短,就返回0
if(nums.length==0)
return 0;
//判断是否出现,数组中不包含target的现象
for(int n :nums)
{
//这里有两个判断
//1 由于数组是非递减数组。数组前面的元素<=后面元素。那么当出现n>target情况出现,且判断2也没有使用上,说明数组中不包含target,返回0
//2 若n==target时,则break
if(n>target)
return 0;
else if(n==target)
break;
}
//res保存结果
int res = 0;
for(int n :nums)
{
//当target出现,n++
if(n == target)
res++;
//若res>0,说明target已出现,后续在出现不为target的元素,没必要再看,break即可。
if(res>0&&n!=target)
break;;
}
return res;
剑指 Offer 53 - II. 0~n-1中缺失的数字
class Solution {
public int missingNumber(int[] nums) {
//排序数组中的搜索问题,首先想到 二分法 解决。
int i = 0,j = nums.length-1;
while (i<=j)
{
int m = (i+j)/2;
if(nums[m] == m)i = m+1;
else j = m-1;
}
return i;
}
}
第 5 天 查找算法(中等)
剑指 Offer 04. 二维数组中的查找
- 由于每一行从左到右递增,每一列从上到下递增
- 因此target>nums[i][m],说明target不在此行中,换下一行寻找。这样提升了查找的效率
class Solution {
public boolean findNumberIn2DArray(int[][] matrix, int target) {
int n = matrix.length;
if(n==0)
return false;
int m = matrix[0].length;
if(m==0)
return false;
for(int i =0;i<n;i++)
{
if(matrix[i][m-1]<target)
{
continue;
}
else
{
for(int j = 0;j<m;j++)
{
if(matrix[i][j]==target)
return true;
}
}
}
return false;
}
}
剑指 Offer 11. 旋转数组的最小数字
method1
- 取巧的方法,用java自带函数
class Solution {
public int minArray(int[] numbers) {
Arrays.sort(numbers);
return numbers[0];
}
}
method2
解析
public int minArray(int[] numbers)
{
int i = 0,j = numbers.length-1;
while (i<j)
{
int mid = (i+j)/2;
if(numbers[mid]>numbers[j])
{
i = mid+1;
}
else if(numbers[mid]<numbers[j]) j = mid;
else return findmin(numbers,i,j);
}
return numbers[i];
}
private int findmin(int [] nums,int start,int end)
{
int res = nums[0];
for(int i =start;i<end;i++)
{
if(nums[i]<res) res = nums[i];
}
return res;
}
剑指 Offer 50. 第一个只出现一次的字符
- hashmap解决问题
方法一
class Solution {
public char firstUniqChar(String s)
{
int n = s.length();
if(n==0) return ' ';
Map<Character,Integer> map = new HashMap<>();
char [] chars = s.toCharArray();
for (char c:chars)
{
if(map.containsKey(c)) map.put(c,map.get(c)+1);
else map.put(c,1);
}
for (char c:chars)
{
if(map.get(c)==1) return c;
}
return ' ';
}
}
方法二
int n = s.length();
if(n==0) return ' ';
Map<Character,Boolean> map = new HashMap<>();
char [] chars = s.toCharArray();
for(char c:chars)
{
map.put(c,!map.containsKey(c));
}
for(char c:chars)
{
if(map.get(c)) return c;
}
return ' ';
第 6 天 搜索与回溯算法(简单)
面试题32 - I. 从上到下打印二叉树
- 定义一个队列,按照左右的顺序依次存储树节点
- 再依次吐出,并存储吐出节点的左右节点
class Solution
{
public int[] levelOrder(TreeNode root)
{
if(root==null) return new int[0];
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
List<Integer> res = new ArrayList<>();
while (!queue.isEmpty())
{
TreeNode node = queue.poll();
res.add(node.val);
if(node.left!=null) queue.add(node.left);
if(node.right!=null) queue.add(node.right);
}
int [] ans = new int[res.size()];
for(int i =0;i<res.size();i++)
ans[i] = res.get(i);
return ans;
}
}
剑指 Offer 32 - II. 从上到下打印二叉树 II
- 与上题一样,将节点保存至队列中
- 如上题不同点在于,按行压入队列
- int [][] res = {};java中定义空的二维数组
class Solution
{
public List<List<Integer>> levelOrder(TreeNode root)
{
Queue<TreeNode> queue = new LinkedList<>();
List<List<Integer>> res = new ArrayList<>();
if(root == null) return res;
queue.add(root);
while (!queue.isEmpty())
{
List<Integer> temp = new ArrayList<>();
int n = queue.size();
for(int i = 0;i<n;i++)
{
TreeNode node = queue.poll();
temp.add(node.val);
if(node.left != null) queue.add(node.left);
if(node.right != null) queue.add(node.right);
}
res.add(temp);
}
return res;
}
}
剑指 Offer 32 - III. 从上到下打印二叉树 III
class Solution
{
public List<List<Integer>> levelOrder(TreeNode root)
{
//定义存储节点的队列
Queue<TreeNode> queue = new LinkedList<>();
//存储最终返回结果的多维列表
List<List<Integer>> res = new ArrayList<>();
//如果树为空,返回空res
if(root==null) return res;
queue.add(root);
//由于希望按之字形打印二叉树,第一行从左到右,第二行从右到左,依次进行
//定义是否翻转该层结果的标志
//先将每一层还是按顺序存储
//判断该层是否为奇数层,则不逆序,若为偶数层,逆序该层元素结果。达到之字形打印目的
int flag = 1;
while (!queue.isEmpty())
{
List<Integer> temp = new ArrayList<>();
int n = queue.size();
//遍历对每一层的节点
for(int i = 0;i<n;i++)
{
//在吐出每一层节点的同时,将他们的子节点也压入
TreeNode node = queue.poll();
temp.add(node.val);
if(node.left!=null) queue.add(node.left);
if(node.right!=null) queue.add(node.right);
}
//若为偶数层,翻转序列
if(flag%2==0) Collections.reverse(temp);
flag++;
res.add(temp);
}
return res;
}
}
第 7 天 搜索与回溯算法(简单)
剑指 Offer 26. 树的子结构
class Solution
{
public boolean isSubStructure(TreeNode A, TreeNode B)
{
return (A != null && B != null) && (recur(A, B) || isSubStructure(A.left, B) || isSubStructure(A.right, B));
}
private boolean recur(TreeNode A, TreeNode B)
{
if(B == null) return true;
if(A == null || A.val != B.val) return false;
return recur(A.left, B.left) && recur(A.right, B.right);
}
}
剑指 Offer 27. 二叉树的镜像
class Solution {
public TreeNode mirrorTree(TreeNode root) {
//判断根节点是否为空,为空返回root
if(root == null)return root;
//递归式的,依次对二叉树的左右子树进行镜像化处理
TreeNode left = mirrorTree(root.left);
TreeNode right = mirrorTree(root.right);
root.left = right;
root.right = left;
return root;
}
}
剑指 Offer 28. 对称的二叉树
class Solution
{
public boolean isSymmetric(TreeNode root) {
return root == null ? true : recur(root.left, root.right);
}
boolean recur(TreeNode L, TreeNode R) {
if(L == null && R == null) return true;
if(L == null || R == null || L.val != R.val) return false;
return recur(L.left, R.right) && recur(L.right, R.left);
}
}
第 8 天 动态规划(简单)
剑指 Offer 10- I. 斐波那契数列
class Solution {
public int fib(int n) {
if(n==0) return 0;
int [] dp = new int[n+2];
dp[0] = 0;
dp[1] =1;
for(int i =2;i<=n;i++)
{
dp[i] = dp[i-2]%1000000007+dp[i-1]%1000000007;
}
return dp[n]%1000000007;
}
}
剑指 Offer 10- II. 青蛙跳台阶问题
class Solution {
public int numWays(int n) {
if(n==0) return 1;
int [] dp = new int[n+2];
dp[0] = 1;
dp[1] = 1;
for(int i =2;i<=n;i++)
{
dp[i] = dp[i-2]%1000000007+dp[i-1]%1000000007;
}
return dp[n]%1000000007;
}
}
剑指 Offer 63. 股票的最大利润
class Solution
{
public int maxProfit(int[] prices)
{
//cost存储最小花费
int cost = Integer.MAX_VALUE,profit = 0;
for(int price:prices)
{
//逐元素排查
//找到最小的花费
cost = Math.min(price,cost);
//找到最小花费下的最大利润
profit = Math.max(price-cost,profit);
}
return profit
}
}
第 9 天 动态规划(中等)
剑指 Offer 42. 连续子数组的最大和
class Solution {
public int maxSubArray(int[] nums) {
int n = nums.length;
//如果数组长度为1,返回nums[0]
if(n==1) return nums[0];
int max_sum =nums[0],cur = 0;
for (int num:nums)
{
cur = Math.max(cur+num,num);
max_sum = Math.max(max_sum,cur);
}
return max_sum;
}
}
剑指 Offer 47. 礼物的最大价值
int row = grid.length;
int col = grid[0].length;
//当数组的大小为0时,返回0
if(row==0||col==0) return 0;
int [][] dp = new int[row+1][col+1];
for(int i =0;i<row;i++)
{
for (int j = 0;j<col;j++)
{
if(i==0&&j==0) continue;
if(i==0)
{
grid[i][j] = grid[i][j]+grid[i][j-1];
continue;
}
else if(j==0)
{
grid[i][j] = grid[i][j]+grid[i-1][j];
continue;
}
grid[i][j] = Math.max(grid[i-1][j],grid[i][j-1])+grid[i][j];
}
}
return grid[row-1][col-1];
第 10 天 动态规划(中等)
剑指 Offer 46. 把数字翻译成字符串
class Solution
{
public int translateNum(int num)
{
/*
* f(i)为第i个位置总共有多少种表示可能
* 若s[i-2:i]在10到25之间,则f(i) = f(i-1)+f(i-2)
* 否则f(i) = f(i-1) */
//将数字转为字符串
String s = String.valueOf(num);
//创建保存结果的数组
int [] dp = new int[s.length()+1];
//当字符串中只有一个数字时,值为1,因此dp[1] = 1;
//dp[2]有两种可能,为2or1
//dp[0]=1,就位dp[2]=2准备着,若条件符合则为2,否则为1
dp[0] = 1;
dp[1] = 1;
for(int i = 2;i<s.length();i++)
{
//获得字符串的最后两位,看是否能组成一个字母
String tmp = s.substring(i-2,i);
if(tmp.compareTo("10")>=0&&tmp.compareTo("25")<=0)
{
dp[i] = dp[i-2]+dp[i-1];
}
else
{
dp[i] = dp[i-1];
}
}
return dp[s.length()];
}
}
第 11 天 双指针(简单)
剑指 Offer 18. 删除链表的节点
class Solution {
public ListNode deleteNode(ListNode head, int val) {
if(head==null) return head;
if(head.val == val)
{
head = head.next;
return head;
}
ListNode pre = head;
ListNode cur = pre.next;
while (cur!=null)
{
if(cur.val==val)
{
pre.next = cur.next;
break;
}
pre = pre.next;
cur = cur.next;
}
return head;
}
}
剑指 Offer 22. 链表中倒数第k个节点
class Solution
{
public ListNode getKthFromEnd(ListNode head, int k)
{
//如果链表为空,返回null
if(head==null) return null;
//顺序遍历链表,统计链表长度
int n = 0;
ListNode count = head;
while (count!=null)
{
n++;
count = count.next;
}
//开始找到倒数第k个节点。如果i+k==n,说明此时是倒数k节点
ListNode temp = head;
int i = 0;
while (temp!=null)
{
if(i+k==n) break;
i++;
temp = temp.next;
}
return temp;
}
}
第 12 天 双指针(简单)
剑指 Offer 25. 合并两个排序的链表
method1
class Solution
{
public ListNode mergeTwoLists(ListNode l1, ListNode l2)
{
ListNode cur = new ListNode(),dum = cur;
cur = l1;
if(l1 == null&&l2 == null)
{
return null;
}
else if(l1 == null&&l2!= null)
{
return l2;
}
else if(l1 != null&&l2 == null)
{
return l1;
}
while(cur!=null)
{
dum = cur;
cur = cur.next;
}
dum.next = l2;
cur = l1;
dum = cur.next;
while (cur!=null)
{
int temp = 0;
dum = cur.next;
while (dum!=null)
{
if(dum.val<cur.val)
{
temp = cur.val;
cur.val = dum.val;
dum.val = temp;
}
dum=dum.next;
}
cur = cur.next;
}
return l1;
}
}
method2
- 双指针
class Solution
{
public ListNode mergeTwoLists(ListNode l1, ListNode l2)
{
if(l1==null&&l2==null) return null;
ListNode dum = new ListNode(0), cur = dum;
while(l1!=null&&l2!=null)
{
if(l1.val>l2.val)
{
cur.next = l2;
l2 = l2.next;
}
else
{
cur.next = l1;
l1 = l1.next;
}
cur = cur.next;
}
cur.next = (l1!=null?l1:l2);
return dum.next;
}
}
剑指 Offer 52. 两个链表的第一个公共节点
method1
- 哈希表解决问题
public class Solution
{
public ListNode getIntersectionNode(ListNode headA, ListNode headB)
{
Map<ListNode,Integer>map = new HashMap<ListNode,Integer>();
ListNode temp = headA;
while (temp!=null)
{
map.put(temp,temp.val);
temp = temp.next;
}
while (headB!=null)
{
if(map.containsKey(headB)==true)
{
return headB;
}
headB = headB.next;
}
return null;
}
}
method2
解析
public class Solution
{
public ListNode getIntersectionNode(ListNode headA, ListNode headB)
{
if(headA==null || headB == null)
return null;
ListNode pA = headA,pB = headB;
while (pA!=pB)
{
pA = (pA == null?headB:pA.next);
pB = (pB == null?headA:pB.next);
}
return pA;
}
}
第 13 天 双指针(简单)
剑指 Offer 21. 调整数组顺序使奇数位于偶数前面
- 双指针解决此问题
class Solution
{
public int[] exchange(int[] nums)
{
int n = nums.length;
//如果数组长度为0,或1,返回
if(n==0 || n==1) return nums;
int i =0,j = n-1;
while (i<j)
{
if(nums[i]%2==1)
{
i++;
continue;
}
else if(nums[j]%2==0)
{
j--;
continue;
}
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
return nums;
}
}
剑指 Offer 57. 和为s的两个数字
method1
- 我的方法
class Solution
{
public int[] twoSum(int[] nums, int target)
{
int n = nums.length;
if(n==1) return nums;
int [] res = new int[2];
int j = n-1;
while (nums[j]>=target)
{
j--;
}
for(int k = j;k>0;k--)
{
boolean flag = false;
for(int i =0;i<k;i++)
{
if(nums[i]+nums[k]==target)
{
res[0] = nums[i];
res[1] = nums[k];
flag = true;
break;
}
else if(nums[i]+nums[k]>target)
{
break;
}
}
if(flag==true)
break;
}
return res;
}
}
method2
- 大佬的方法,对撞双指针 解析
class Solution
{
public int[] twoSum(int[] nums, int target)
{
int n = nums.length;
int i = 0,j = n-1;
while (i<j)
{
int temp = nums[i]+nums[j];
if(temp<target) i++;
else if(temp>target) j--;
else return new int[] {nums[i],nums[j]};
}
return new int[0];
}
}
剑指 Offer 58 - I. 翻转单词顺序
- s.trim() 删除首尾空格
- StringBuilder类
s = s.trim(); // 删除首尾空格
int j = s.length()-1,i=j;
//当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。
//和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。
StringBuilder res = new StringBuilder();
while (i>=0)
{
while (i>=0&&s.charAt(i)!=' ')i--;//找到第一个单词
res.append(s.substring(i+1,j+1)+" ");//添加进结果中
while (i>=0&&s.charAt(i)==' ')i--;//跳过两元素之间的空格
j =i;
}
return res.toString().trim();//将StringBuilder转为String类,并去除首位多余空格
第 14 天 搜索与回溯算法(中等)
剑指 Offer 12. 矩阵中的路径
class Solution {
public boolean exist(char[][] board, String word)
{
char [] words = word.toCharArray();
for(int i = 0;i<board.length;i++)
{
for(int j = 0;j<board[i].length;j++)
{
//利用深度搜索(DFS),判断矩阵是否包含目标单词
if(dfs(board,words,i,j,0)) return true;
}
}
return false;
}
private boolean dfs(char[][] board,char[] word,int i,int j,int k)
{
/*
* 出现以下3种情况,则返回false
* 当i的值大于等于数组宽,或i小于0
* 当j的值大于等于数组长,或小于0
* 或在board[i][j]!=word[k]
*/
if(i>=board.length||i<0||j>=board[0].length||j<0||board[i][j]!=word[k]) return false;
//若不为false,且k==word.length
//表示数组已经满足条件
//若k!=word.length,则继续寻找剩余的单词
if(k == word.length-1) return true;
//并将本轮已经查看过的board[i][j]置空
board[i][j] = '\0';
boolean res = dfs(board,word,i+1,j,k+1)||dfs(board,word,i,j+1,k+1)||dfs(board,word,i-1,j,k+1)||dfs(board,word,i,j-1,k+1);
//对已经查看过的元素,我们让其恢复本来值
board[i][j] = word[k];
return res;
}
}
剑指 Offer 13. 机器人的运动范围
class Solution
{
int m,n,k;
boolean[][] visited;
public int movingCount(int m, int n, int k)
{
this.m = m;this.n = n;this.k = k;
this.visited = new boolean[m][n];
return dfs(0,0,m,n);
}
public int dfs(int i,int j,int m,int n)
{
if(i>=m||j>=n||visited[i][j]||bitSum(i)+bitSum(j)>k) return 0;
visited[i][j] = true;
return 1+dfs(i+1,j,m,n)+dfs(i,j+1,m,n);
}
private int bitSum(int n)
{
int sum = 0;
while(n > 0) {
sum += n % 10;
n /= 10;
}
return sum;
}
}
第 15 天 搜索与回溯算法(中等)
剑指 Offer 34. 二叉树中和为某一值的路径
class Solution
{
//保存最终结果
LinkedList<List<Integer>> res = new LinkedList<>();
//遍历每一个分支时,保存的结果
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> pathSum(TreeNode root, int target)
{
recur(root,tar);
return res;
}
void recur(TreeNode root, int tar)
{
if (root == null) return;
path.add(root.val);
tar -= root.val;
if (tar == 0 && root.left == null && root.right == null)
res.add(new LinkedList(path));
recur(root.left, tar);
recur(root.right, tar);
path.removeLast();
}
}
剑指 Offer 54. 二叉搜索树的第k大节点
method1
- 先将所有节点遍历
- 再从所有节点中,找到需要的值
class Solution
{
List<Integer> res = new ArrayList<>();
public int kthLargest(TreeNode root, int k)
{
recur(root);
Collections.sort(res);
Collections.reverse(res);
System.out.println(res);
return res.get(k-1);
}
void recur(TreeNode root)
{
if(root==null) return;
recur(root.left);
res.add(root.val);
recur(root.right);
}
}
第 16 天 排序(简单)
剑指 Offer 61. 扑克牌中的顺子
method1
- 哈希表
{
public boolean isStraight(int[] nums)
{
//该数组元素为顺子的重复条件是,除过0以为,其它元素不能重复
//同时max-min<5,如果最大值与最小值分别为 1,6 则返回false
//定义Set集合,其中元素不重复
Set<Integer> repeat = new HashSet<>();
int max = 0,min = 15;
for(int num:nums)
{
if(num==0) continue;
max = Math.max(max,num);
min = Math.min(min,num);
if(repeat.contains(num)) return false;
repeat.add(num);
}
return max-min<5;
}
}
method2
- 排序
class Solution
{
public boolean isStraight(int[] nums)
{
int joker = 0;
Arrays.sort(nums);
for(int i =0;i<4;i++)
{
if(nums[i]==0) joker++;
else if(nums[i]==nums[i+1]) return false;
}
return nums[4]-nums[joker]<5;
}
}
第 17 天 排序(中等)
剑指 Offer 40. 最小的k个数
method1
- 使用java库函数,偷懒解决问题
class Solution
{
public int[] getLeastNumbers(int[] arr, int k)
{
int n = arr.length;
if (n==0) return new int[0];
Arrays.sort(arr);
return Arrays.copyOfRange(arr,0,k);
}
}
剑指 Offer 41. 数据流中的中位数
method1
- 暴力法
class MedianFinder {
List<Integer> list = new ArrayList<>();
/** initialize your data structure here. */
public MedianFinder()
{
this.list = list;
}
public void addNum(int num)
{
list.add(num);
}
public double findMedian()
{
Collections.sort(list);
int n = list.size();
if(n==0) return 0.0;
if(n%2==0)
{
return (list.get(n/2-1)+list.get(n/2))/2.0;
}
else
{
return list.get(n/2);
}
}
}
第 18 天 搜索与回溯算法(中等)
剑指 Offer 55 - I. 二叉树的深度
- 递归解决此问题
class Solution
{
public int maxDepth(TreeNode root)
{
if(root==null) return 0;
return Math.max(recur(root.left)+1,recur(root.right)+1);
}
public int recur(TreeNode root)
{
if(root==null) return 0;
return Math.max(recur(root.left)+1,recur(root.right)+1);
}
}
剑指 Offer 55 - II. 平衡二叉树
*看不懂,挺难的
class Solution {
public boolean isBalanced(TreeNode root)
{
return recur(root)!=-1;
}
private int recur(TreeNode root)
{
if(root==null) return 0;
int left = recur(root.left);
if(left==-1)return -1;
int right = recur(root.right);
if(right==-1) return -1;
return Math.abs(left-right)<2?Math.max(left,right)+1:-1;
}
}
第 19 天 搜索与回溯算法(中等)
剑指 Offer 64. 求1+2+…+n
- 题目堵住了大多数现成方法
- 因此使用逻辑运算&&的特性
- A&&B中,如果A为非,则B不执行。若A为正,则B才执行。
题目要求为:求 1+2+...+n ,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
输入: n = 3
输出: 6
输入: n = 9
输出: 45
class Solution
{
public int sumNums(int n)
{
boolean flag = n>0&&(n+=sumNums(n-1))>0;
return n;
}
}
剑指 Offer 68 - I. 二叉搜索树的最近公共祖先
- 二叉树为搜索树,即整棵树满足左子树节点的值<根节点<右子树节点的值
- 使用遍历的思想解决此问题
- 首先当root!=null进入循环,这里保证了root初始不为空的情况,若初始即为空,就返回null
- 当p,q的val均大于root.val,说明p,q都处于root的右子树,则在root的右子树中找p,q
- 当p,q的val均小于root.val,说明p,q都处于root的左子树,则在root的左子树中找p,q
- 当不是以上两种情况是,说明找到最近公共祖先,break循环
class Solution
{
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q)
{
while (root!=null)
{
if(root.val<p.val&&root.val<q.val)//p,q都在root的右子树
root = root.right;//遍历root的右子节点
else if(root.val>p.val&&root.val>q.val)//p,q都在root的左子树
root = root.left;
else break;//root.val == p.val || root.val == q.val
}
return root;
}
}
第 21 天 位运算(简单)
剑指 Offer 15. 二进制中1的个数
- n&0 = 0代表最后一位为0
- n&1 = 1代表最后一位为1
- java中无符号树右移一位n>>>=1;
public class Solution
{
// you need to treat n as an unsigned value
public int hammingWeight(int n)
{
int res = 0;
//n可能为正数或负数,因此此处while循环退出的条件为n!=0
while (n!=0)
{
res+=n&1;
n>>>=1;
}
return res;
}
}
剑指 Offer 65. 不用加减乘除做加法
- 这题挺难的
- 解析
class Solution
{
public int add(int a, int b)
{
int sum = a^b;
int carry = (a&b)<<1;
while (carry!=0)
{
a = sum;
b = carry;
sum = a^b;
carry = (a&b)<<1;
}
return sum;
}
}
第 22 天 位运算(中等)
剑指 Offer 56 - II. 数组中数字出现的次数 II
- 哈希表
class Solution
{
public int singleNumber(int[] nums)
{
int n = nums.length;
if(n==1) return nums[0];
Map<Integer,Integer> map = new HashMap<>();
for(int i = 0;i<n;i++)
{
if(!map.containsKey(nums[i]))
{
map.put(nums[i],1);
}
else
{
map.put(nums[i], map.get(nums[0])+1);
}
}
for(int i =0;i<n;i++)
{
if(map.get(nums[i])==1)
{
return nums[i];
}
}
return 0;
}
}
动态规划
118. 杨辉三角
method1
- 无脑版1
class Solution
{
public List<List<Integer>> generate(int numRows)
{
int [][] res = new int[numRows][numRows];
res[0][0] = 1;
List<List<Integer>> ans = new ArrayList<>();
List<Integer> temp = new ArrayList<>();
temp.add(1);
ans.add(temp);
if(numRows==1)
{
return ans;
}
res[1][0]=1;res[1][1] = 1;
for (int i =2;i<numRows;i++)
{
res[i][0] = 1;
for(int j = 1;j<i;j++)
{
res[i][j] = res[i-1][j]+res[i-1][j-1];
}
res[i][i] = 1;
}
for(int i = 1;i<numRows;i++)
{
List<Integer> t = new ArrayList<>();
for(int j =0;j<=i;j++)
{
t.add(res[i][j]);
}
ans.add(t);
}
return ans;
}
}
method2
- 无脑版2
class Solution {
public List<List<Integer>> generate(int numRows) {
List<List<Integer>> res = new ArrayList<>();
for(int i =0;i<numRows;i++)
{
List<Integer> temp = new ArrayList<>();
for(int j = 0;j<=i;j++)
{
if(j==0||j==i) temp.add(1);
else
{
temp.add(res.get(i-1).get(j)+res.get(i-1).get(j-1));
}
}
res.add(temp);
}
return res;
}
}
剑指 Offer 48. 最长不含重复字符的子字符串
- 定义一个哈希表来完成此任务
- 一个长度为N的字符串,其子字符串长度为(1+N)N/2
- 遍历输入字符串s
class Solution
{
public int lengthOfLongestSubstring(String s)
{
Map<Character,Integer> map = new HashMap<>();
int res = 0,tmp = 0;
for(int j = 0;j<s.length();j++)
{
int i = map.getOrDefault(s.charAt(j),-1);//获取索引i 如果该key值在map中,返回其位置,否则返回-1
map.put(s.charAt(j),j);//更新哈希表
tmp = tmp<j-i?tmp+1:j-i;
res = Math.max(res,tmp);
}
return res;
}
}
198. 打家劫舍
- 该题的基本思想就是dp[i] = max(dp[i-2]+nums[i],dp[i-1])
- 我与官方的基本思想完全一致,不同点在于dp[1]的取值,官方的思路是正确的
- 我的错误点在于,没有在dp[1]部分应用,dp[i]的值是从开始到第i位置的最大合这一思想
官方
class Solution
{
public int rob(int[] nums)
{
int n = nums.length;
if(n == 1)
return nums[0];
int [] dp = new int[n];
dp[0] = nums[0];
dp[1] = Math.max(nums[0],nums[1]);
for(int i = 2;i<n;i++)
{
dp[i] = Math.max(dp[i-2]+nums[i],dp[i-1]);
}
return dp[n-1];
}
}
我的
class Solution {
public int rob(int[] nums) {
int n = nums.length;
if(n == 1)
return nums[0];
int [] dp = new int[n];
dp[0] = nums[0];
dp[1] = nums[1];
if(n==2)
return Math.max(dp[1],dp[0]);
for(int i = 2;i<n;i++)
{
dp[i] = Math.max(dp[i-2]+nums[i],dp[i-1]);
}
return dp[n-1];
}
}
213. 打家劫舍 II
- 与上一题相似,但是这里的问题是,第一间房子和最后一间房子只能选一个。
- 因此在nums[0,n-1]与nums[1,n]中挑出最大的即可。使用到方法就是上一题的方法
class Solution {
public int rob(int[] nums)
{
int n = nums.length;
if(n==1)
return nums[0];
if(n==2)
return Math.max(nums[0],nums[1]);
return Math.max(MyRob(Arrays.copyOfRange(nums,0,nums.length-1)),MyRob(Arrays.copyOfRange(nums,1,n)));
}
private int MyRob(int [] nums)
{
int n = nums.length;
if(n==1)
return nums[0];
int [] dp = new int[n];
dp[0] = nums[0];
dp[1] = Math.max(nums[0],nums[1]);
for(int i = 2;i<n;i++)
{
dp[i] = Math.max(dp[i-2]+nums[i],dp[i-1]);
}
return dp[n-1];
}
}
55. 跳跃游戏
- [2,3,1,1,4]
- 当从位置0开始,可以去到位置1或2,最远距离为2。若去到位置1,从位置1最远距离可以渠道1+3=4位置,也就是最后一位,因此该数组成立
- [3,2,1,0,4]
- 从位置0开始,最远可以去到下标为3,即值为0的位置,但是从位置3开始,不能在往下走。因此先去下标1处,从下标1处最远也是可以去下标3,因此该方法不行。尝试从下标2开始,从下标2也只能去下标3,因此本数组不成立。
- 总之,若设能去到的最远距离为farest
- farest = farest = Math.max(farest,i+nums[i]);
- farest>=n-1,则成立
class Solution {
public boolean canJump(int[] nums) {
int n = nums.length;
int farest = 0;
for(int i = 0;i<n;i++)
{
if(i<=farest)
{
farest = Math.max(farest,i+nums[i]);
if(farest>=n-1) return true;
}
}
return false;
}
}
45. 跳跃游戏 II
class Solution
{
public int jump(int[] nums)
{
int n = nums.length;
int end = 0;
int farest = 0;
int steps = 0;
for(int i =0;i<n-1;i++)
{
farest = Math.max(farest,i+nums[i]);
if(i==end)
{
end = farest;
steps++;
}
}
return steps;
}
}
53. 最大子数组和
class Solution
{
public int maxSubArray(int[] nums)
{
int n = nums.length;
//如果数组长度为1,返回nums[0]
if(n==1) return nums[0];
//dp数组存储结果
int [] dp = new int[n];
int max = nums[0];
dp[0] = nums[0];
for(int i = 1;i<n;i++)
{
//每个位置dp[i] = Math.max(dp[i-1]+nums[i],nums[i]);
//Math.max(dp[i-1]+nums[i],nums[i])保证了当前位置的最大值,是连续的,或者是其元素值本身
dp[i] = Math.max(dp[i-1]+nums[i],nums[i]);
//使用max更新,最大的子数组和
if(dp[i]>max) max = dp[i];
}
return max;
}
}
918. 环形子数组的最大和
class Solution {
public int maxSubarraySumCircular(int[] nums) {
int total = 0,maxSum = nums[0],curMax = 0, minSum = nums[0], curMin = 0;
for(int a :nums)
{
curMax = Math.max(curMax+a,a);
maxSum = Math.max(maxSum,curMax);
curMin = Math.min(curMin+a,a);
minSum = Math.min(minSum,curMin);
total+=a;
}
return maxSum>0?Math.max(maxSum,total-minSum):maxSum;
}
}
122. 买卖股票的最佳时机 II
- 如果数组中值是严格单调递增的,那么结果为num[0]-num[last] = 每两个相邻元素之差和
- 否则的话,就看后一个元素是否大于前一个元素,若是则相减
解析
class Solution
{
public int maxProfit(int[] prices)
{
int profit = 0;
for(int i=1;i<prices.length;i++)
{
int temp = prices[i]-prices[i-1];
if(temp>0) profit+=temp;
}
return profit;
}
}
第16天 排序(简单)
剑指 Offer 45. 把数组排成最小的数
- 对数组arr进行排序
- 将结果保存数组res中
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
int [] res = new int[k];
//java自带排序算法
Arrays.sort(arr);
for(int i = 0;i<k;i++)
{
res[i] = arr[i];
}
return res;
}
}
121. 买卖股票的最佳时机
- 找到最小cost
- 找到最大price
- 两者相减即为最大利润
class Solution
{
public int maxProfit(int[] prices)
{
int n = prices.length;
if(n==1) return 0;
int cost = prices[0],profit = 0,temp = 0;
for(int price:prices)
{
cost = Math.min(cost,price);
temp = price-cost;
profit = Math.max(temp,profit);
}
return profit;
}
}
第 18 天 搜索与回溯算法(中等)
剑指 Offer 55 - I. 二叉树的深度
- 后序遍历
- 若root为空返回0
- 返回每个子树的深度,将最大子树深度值加1,即就是二叉树的深度值
/**
* 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;
return Math.max(maxDepth(root.left),maxDepth(root.right))+1;
}
}
剑指 Offer 55 - II. 平衡二叉树
- 若root为空返回true
- 判断从根节点为启示,左右子树是否为平衡二叉树
- 再分布判断左右子树内部是否也为平衡二叉树
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public boolean isBalanced(TreeNode root)
{
if(root == null) return true;
return Math.abs(maxDepth(root.left)-maxDepth(root.right))<=1&&isBalanced(root.left)&&isBalanced(root.right);
}
public int maxDepth(TreeNode root)
{
if(root == null) return 0;
return Math.max(maxDepth(root.left),maxDepth(root.right))+1;
}
}
杂项
计算最大公约数
method1
static int gcd(int a, int b)
{
return b == 0 ? a : gcd(b, a % b);
}
method2
static int gcd(int a, int b)
{
if(b == 0)
return a;
while (a%b!=0)
{
int temp = a%b;
a = b;
b= temp;
}
return b;
}
最小公倍数
public static int lcm(int a,int b)
{
return a*b/gcd(a,b);
}