动态规划
剑指offer
7. 斐波那契数列
public class Solution {
public int Fibonacci(int n) {
if(n == 0){
return 0;
}
if(n==1){
return 1;
}
int[] dp = new int[n+1];
dp[0] = 0;
dp[1] = 1;
for (int k=2; k<=n; k++){
dp[k] = dp[k-1]+dp[k-2];
}
return dp[n];
}
}
8. 跳台阶
public class Solution {
public int JumpFloor(int target) {
if(target == 0){
return 0;
}
if(target == 1){
return 1;
}
//定义状态:dp[i] 为跳上第i阶阶梯的调法
int[] dp = new int[target+1];
dp[1] = 1;
dp[2] = 2;
for(int k = 3; k<=target; k++){
dp[k] = dp[k-1] + dp[k-2];
}
return dp[target];
}
}
9.变态跳台阶
public class Solution {
public int JumpFloorII(int target) {
if(target == 0){
return 0;
}
if(target == 1){
return 1;
}
//确定状态,dp[i] 表示跳到第i阶台阶的跳发
int[] dp = new int[target+1];
dp[1] = 1;
dp[2] = 2;
for(int k=3; k<=target; k++){
dp[k] = 1; //直接跳到第k阶台阶,属于一种跳发
for(int n=1; n<k; n++){
dp[k] = dp[k]+dp[n];
}
}
return dp[target];
}
}
10.矩形覆盖
public class Solution {
public int RectCover(int target) {
if(target == 0){
return 0;
}
if(target == 1){
return 1;
}
int[] dp = new int[target+1];
dp[1] = 1;
dp[2] = 2;
for(int k=3; k<=target; k++){
dp[k] = dp[k-1] + dp[k-2];
}
return dp[target];
}
}
30.连续子数组的最大和
public class Solution {
public int FindGreatestSumOfSubArray(int[] array) {
if(array.length == 0){
return 0;
}
int[] dp = new int[array.length];
dp[0] = array[0];
int max = array[0];
for(int k=1; k<array.length; k++){
dp[k] = Math.max(array[k], dp[k-1]+array[k]);
max = Math.max(max, dp[k]);
}
return max;
}
}
33.丑数
思路:
丑数 N = k2 * 2 + k3 * 3 + k5 * 5 (k2, k3 , k5 个数不定)
public class Solution {
public int GetUglyNumber_Solution(int index) {
if(index == 0){
return 0;
}
int[] dp = new int[index+1];
dp[1] = 1; // dp[k]表示从小到大的第k个丑数
int k2 = 1, k3 = 1, k5 = 1; // 丑数 N = k2 *2 + k3 * 3 + k5 *5
for(int k=2; k<=index; k++){
dp[k] = Math.min(dp[k2]*2, Math.min(dp[k3]*3, dp[k5]*5));
if(dp[k] == dp[k2]*2){
k2++;
}
if(dp[k] == dp[k3]*3){
k3++;
}
if(dp[k] == dp[k5]*5){
k5++;
}
}
return dp[index];
}
}
67. 剪绳子
public class Solution {
public int cutRope(int target) {
int[] dp = new int[target+1];
dp[1] = 1;
// i*(k-i) 表示把k分成两个数,
// i*dp[k-i]把k至少分成三个数,因为dp[k-i]的结果是把k-i至少分成了两个数
for(int k=2; k<=target; k++){
for(int i=1; i<k; i++){
dp[k] = Math.max(dp[k], Math.max(i*(k-i), i*dp[k-i]));
}
}
return dp[target];
}
}
leetcode
53.最大子序和
class Solution {
//动态规划
public int maxSubArray(int[] nums) {
int n = nums.length;
if(n==0){
return 0;
}
//定义状态,dp[i]表示以第i个元素为结尾的连续子序列的最大和
int[] dp = new int[n];
//状态初始化
dp[0] = nums[0];
int max = nums[0];
//以第k个元素结尾的连续子序列的最大和要么是第k个元素本身,要么是第k个元素与以第k-1个元素结尾的子序列和的和
//状态转移方程:dp[k] = Math.max(nums[k], nums[k]+dp[k-1]);
for(int k=1; k<n; k++){
dp[k] = Math.max(nums[k], nums[k]+dp[k-1]);
max = Math.max(dp[k], max);
}
return max;
}
}
121.买卖股票的最佳时机
class Solution {
public int maxProfit(int[] prices) {
int n = prices.length;
if(n == 0){
return 0;
}
//定义状态,dp[k]表示第k天可以获得的最大利润
int[] dp = new int[n];
dp[0] = 0;
int min = prices[0]; //记录前k-1天中,股票的最低价
int max = dp[0]; //记录k天中可获得的最大利润
//状态转移方程:第k天可以获得的最大利润 = max(第k-1天可以获得的最大利润, 第k天价格 -前k-1天中价格最小值)
for(int k=1; k<n; k++){
dp[k] = Math.max(dp[k-1], prices[k]-min);
min = Math.min(min, prices[k]);
max = Math.max(max, dp[k]);
}
return max;
}
}
343.整数拆分
class Solution {
public int integerBreak(int n) {
//确定状态:dp[k] 表示拆分正整数k后可获得的最大积 k>=2
int[] dp = new int[n+1];
dp[1] = 1;
dp[2] = 1; //2=1+1; 1*1=1
//状态转移方程:dp[k] = max( i*(k-i), i*dp[k-i] ) , 其中 1<=i<k
for(int k=3; k<=n; k++){
dp[k] = 0;
for(int i=1; i<k; i++){
int temp = Math.max(i*(k-i), i*dp[k-i]);
dp[k] = Math.max(temp, dp[k]);
}
}
return dp[n];
}
}
198.打家劫舍
class Solution {
public int rob(int[] nums) {
int n = nums.length;
if(n == 0){
return 0;
}
//定义状态,dp[k] 为考虑盗取第[0,k]号房子所取得的最大收益
int[] dp = new int[n];
dp[0] = nums[0];
//状态转移方程,求解dp[k], dp[k] = max( nums[k]+dp[k-2], nums[k-1]+dp[k-3]... )
for(int k=1; k<n; k++){
dp[k] = 0;
for(int i = k; i>=0; i--){
dp[k] = Math.max((nums[i]+(i>=2 ? dp[i-2]:0)), dp[k]);
}
}
return dp[n-1];
}
}
链表
剑指offer
3.从尾到头打印链表
/**
* public class ListNode {
* int val;
* ListNode next = null;
*
* ListNode(int val) {
* this.val = val;
* }
* }
*
*/
import java.util.*;
public class Solution {
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> res = new ArrayList<>();
if (listNode == null){
return res;
}
Stack<Integer> stack = new Stack<>();
while(listNode != null){
stack.push(listNode.val);
listNode = listNode.next;
}
int n = stack.size(); //需单独保存一下
for(int i = 0; i<n; i++){
res.add(stack.pop());
}
return res;
}
}
14.链表中倒数第k个结点
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode FindKthToTail(ListNode head,int k) {
if (head == null){
return null;
}
if(k == 0){
return null;
}
ListNode slow = head;
ListNode fast = head;
// fast 最终要只想链表的最后一个非空节点
for(int i=0; i<k-1; i++){
fast = fast.next;
if(fast == null){
return null;
}
}
while(fast != null && fast.next != null){
slow = slow.next;
fast = fast.next;
}
return slow;
}
}
15.反转链表
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode ReverseList(ListNode head) {
if(head == null){
return null;
}
ListNode pre = null;
ListNode cur = head;
while(cur != null){
ListNode sub = cur.next;
cur.next = pre;
pre = cur;
cur = sub;
}
return pre;
}
}
16.合并两个排序的链表
非递归(归并法)解法
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
if(list1 == null){
return list2;
}
if(list2 == null){
return list1;
}
ListNode dummyHead = new ListNode(-1);
ListNode cur = dummyHead;
while(list1 != null || list2 != null){
if (list1 == null){
cur.next = list2;
cur = cur.next;
list2 = list2.next;
} else if(list2 == null){
cur.next = list1;
cur = cur.next;
list1 = list1.next;
} else {
if(list1.val < list2.val){
cur.next = list1;
cur = cur.next;
list1 = list1.next;
}else {
cur.next = list2;
cur = cur.next;
list2 = list2.next;
}
}
}
return dummyHead.next;
}
}
递归解法
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
if(list1 == null){
return list2;
}
if(list2 == null){
return list1;
}
ListNode node = null;
if(list1.val < list2.val){
node = list1;
node.next = Merge(list1.next, list2);
}else {
node = list2;
node.next = Merge(list1, list2.next);
}
return node;
}
}
25.复杂链表的复制
/*
public class RandomListNode {
int label;
RandomListNode next = null;
RandomListNode random = null;
RandomListNode(int label) {
this.label = label;
}
}
*/
public class Solution {
public RandomListNode Clone(RandomListNode pHead)
{
if(pHead == null){
return null;
}
// 1.复制 A->B->C => A->A'->B->B'->C->C'
RandomListNode cur = pHead;
while(cur != null){
RandomListNode node = new RandomListNode(cur.label);
node.next = cur.next;
cur.next = node;
cur = cur.next.next;
}
// 2.复制random
cur = pHead;
while(cur != null){
if(cur.random != null){
cur.next.random = cur.random.next;
}
cur = cur.next.next;
}
// 3.拆分,需要不破坏原链表,拆分成两个独立的链表
cur = pHead;
RandomListNode newCur = pHead.next;
RandomListNode newHead = pHead.next;
while(cur != null){
cur.next = cur.next.next;
if(newCur.next != null){
newCur.next = newCur.next.next;
}
cur = cur.next;
newCur = newCur.next;
}
return newHead;
}
}
36.两个链表的第一个公共结点
思路1: 找出2个链表的长度,然后让长的先走两个链表的长度差,然后再一起走
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
if (pHead1 == null || pHead2 == null){
return null;
}
int len1 = 0;
ListNode cur1 = pHead1;
while(cur1 != null){
len1++;
cur1 = cur1.next;
}
int len2 = 0;
ListNode cur2 = pHead2;
while(cur2 != null){
len2++;
cur2 = cur2.next;
}
int n = Math.abs(len1-len2);
if(len1>len2){
cur1 = pHead1;
for(int i=0; i<n; i++){
cur1 = cur1.next;
}
cur2 = pHead2;
}else{
cur2 = pHead2;
for(int i=0; i<n; i++){
cur2 = cur2.next;
}
cur1 = pHead1;
}
while(cur1 != null){
if(cur1 == cur2){
return cur1;
}
cur1 = cur1.next;
cur2 = cur2.next;
}
return null;
}
}
思路2:
假定 List1长度: a+n , List2 长度:b+n,n为两个链表的公共部分
让cur1走路程a+n+b+n,cur2走路程b+n+a+n, 若有公共部分,则cur1与cur2在走开始最后一段路程n时会相遇
时间复杂度O(n+a+b)
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
ListNode cur1 = pHead1;
ListNode cur2 = pHead2;
while(cur1 != cur2){
cur1 = (cur1 == null? pHead2 : cur1.next);
cur2 = (cur2 == null? pHead1 : cur2.next);
}
return cur1;
}
}
思路3:
如果存在共同节点的话,那么从该节点,两个链表之后的元素都是相同的。
可以用两个栈分别来装这两条链表,然后一个一个比较pop出来的值,找到第一个相同的节点。
55.链表中环的入口结点
思路:
设起点距环的入口的距离为n, 环的长度为r
fast走的距离为:2L=n+ar, slow走的距离为:L=n+br
可得,2L-L =L=(a-b)r
即:slow走了(a-b)个环的距离。
若slow从环入口出发,走过L距离后,恰好回到环入口;
所以当slow从起点出发,走过L距离后,在环内且距环入口的距离为n,恰好是起点距环入口的距离
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public ListNode EntryNodeOfLoop(ListNode pHead)
{
// 成环至少需要两个节点
if(pHead == null || pHead.next == null){
return null;
}
ListNode fast = pHead;
ListNode slow = pHead;
// 不成环时,会从此循环中退出
while(fast != null && fast.next.next != null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow){
// 第一次相遇后
fast = pHead;
while(fast != slow){
fast = fast.next;
slow = slow.next;
}
return fast;
}
}
return null;
}
}
56.删除链表中重复的结点
思路:
创建虚拟头结点,以便对头结点作统一处理
设置两个指针,慢指针指向确定的不重复的节点,快指针为当前做判断的工作节点
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public ListNode deleteDuplication(ListNode pHead)
{
if(pHead == null){
return null;
}
ListNode dummyHead = new ListNode(-1);
dummyHead.next = pHead;
ListNode pre = dummyHead;
ListNode cur = pHead;
while(cur != null && cur.next != null){
if(cur.next.val == cur.val){
int val = cur.val;
while(cur != null && cur.val == val){
cur = cur.next;
}
pre.next = cur;
}else {
pre = cur;
cur = cur.next;
}
}
return dummyHead.next;
}
}
二叉树
剑指offer
4.重建二叉树
先找出根节点,然后使用递归重构
/**
* Definition for binary tree
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
import java.util.*;
public class Solution {
public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
if(pre.length == 0 || in.length == 0 || pre.length != in.length){
return null;
}
TreeNode root = new TreeNode(pre[0]);
int index = 0;
while(in[index] != pre[0]){
index++;
}
int[] leftPre = new int[index];
int[] leftIn = new int[index];
leftPre = Arrays.copyOfRange(pre,1, index+1);
leftIn = Arrays.copyOfRange(in, 0, index);
int[] rightPre = new int[pre.length-index-1];
int[] rightIn = new int[in.length-index-1];
rightPre = Arrays.copyOfRange(pre, index+1, pre.length);
rightIn = Arrays.copyOfRange(in, index+1, in.length);
root.left = reConstructBinaryTree(leftPre, leftIn);
root.right = reConstructBinaryTree(rightPre, rightIn);
return root;
}
}
17.树的子结构
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public boolean HasSubtree(TreeNode root1,TreeNode root2) {
if(root1 == null || root2 == null){
return false;
}
boolean res = false;
if(root1.val == root2.val){
res = isContains(root1, root2);
}
if(!res){
res = HasSubtree(root1.left, root2) || HasSubtree(root1.right, root2);
}
return res;
}
// 判断Tree1是否包含(不一定相等)Tree2,从两棵树的根节点依次往下比较,递归实现
public boolean isContains(TreeNode root1, TreeNode root2){
//root1==null, root2==null 为true; root1!=null,root2==null 为true
if(root2 == null){
return true;
}
// 此时root2必不为null,故root1==null 为false
if(root1 == null){
return false;
}
if(root1.val != root2.val){
return false;
}
return isContains(root1.left, root2.left) && isContains(root1.right, root2.right);
}
}
18.二叉树的镜像
递归或非递归(借助栈)来实现
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public void Mirror(TreeNode root) {
if(root == null){
return;
}
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
Mirror(root.left);
Mirror(root.right);
}
}
23.二叉搜索树的后序遍历序列
import java.util.*;
public class Solution {
private int flag = 0; // 记录是否是首次进入
public boolean VerifySquenceOfBST(int [] sequence) {
int len = sequence.length;
if(len == 0 && flag == 0){
return false;
}
if(len == 0 && flag == 1){
return true;
}
flag = 1;
int last = sequence[len-1];
int index = 0;
for(int i=0; i<len-1; i++){
if(sequence[i]<last){
index++;
}
}
for(int i=index; i<len-1; i++){
if(sequence[i]<last){
return false;
}
}
return VerifySquenceOfBST(Arrays.copyOfRange(sequence,0,index)) &&
VerifySquenceOfBST(Arrays.copyOfRange(sequence,index,len-1));
}
}
24.二叉树中和为某一值的路径
import java.util.ArrayList;
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
private ArrayList<ArrayList<Integer>> res = new ArrayList<ArrayList<Integer>>();
private ArrayList<Integer> list = new ArrayList<>();
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
if(root == null){
return res;
}
list.add(root.val);
target = target-root.val;
if(root.left == null && root.right == null && target == 0){
res.add(new ArrayList<Integer>(list));
}else{
FindPath(root.left, target);
FindPath(root.right, target);
}
list.remove(list.size()-1);
return res;
}
}
26.二叉搜索树与双向链表
二叉搜索树中序遍历就是排好序的结果
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public TreeNode Convert(TreeNode pRootOfTree) {
if(pRootOfTree == null){
return null;
}
TreeNode head = sub(pRootOfTree, null);
while(head != null && head.left != null){
head = head.left;
}
return head;
}
// 中序遍历
private TreeNode sub(TreeNode root, TreeNode last){
if(root == null){
return null;
}
if(root.left != null){
last = sub(root.left, last);
}
root.left = last;
if(last != null){
last.right = root;
}
last = root;
if(root.right != null){
last = sub(root.right, last);
}
return last;
}
}
38.二叉树的深度
利用递归遍历分别返回左右子树深度
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public int TreeDepth(TreeNode root) {
if(root == null){
return 0;
}
return Math.max(TreeDepth(root.left), TreeDepth(root.right))+1;
}
}
39.平衡二叉树
利用二叉树的深度, 判断是否平衡因子的绝对值<= 1.
public class Solution {
public boolean IsBalanced_Solution(TreeNode root) {
if(root == null){
return true;
}
int l = getDepth(root.left);
int r = getDepth(root.right);
return Math.abs(l-r)<=1;
}
// 求一棵树的深度
private int getDepth(TreeNode root){
if(root == null){
return 0;
}
return Math.max(getDepth(root.left), getDepth(root.right))+1;
}
}
进一步,可以通过减枝降低时间复杂度
public class Solution {
public boolean IsBalanced_Solution(TreeNode root) {
if(root == null){
return true;
}
return getDepth(root)!=-1;
}
private int getDepth(TreeNode root){
if(root == null){
return 0;
}
// 减枝
if(getDepth(root.left) == -1 || getDepth(root.right) == -1){
return -1;
}
return Math.abs(getDepth(root.left)-getDepth(root.right))>1 ?
-1 : Math.max(getDepth(root.left), getDepth(root.right)) + 1;
}
}
57.二叉树的下一个结点
/*
public class TreeLinkNode {
int val;
TreeLinkNode left = null;
TreeLinkNode right = null;
TreeLinkNode next = null;
TreeLinkNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public TreeLinkNode GetNext(TreeLinkNode pNode)
{
if(pNode == null){
return null;
}
if(pNode.right != null){
pNode = pNode.right;
while(pNode.left != null){
pNode = pNode.left;
}
return pNode;
}
while(pNode.next != null){
if(pNode.next.left == pNode){
return pNode.next;
}
pNode = pNode.next;
}
return null;
}
}
58.对称的二叉树
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
boolean isSymmetrical(TreeNode pRoot)
{
if(pRoot == null){
return true;
}
return subIsSymmetrical(pRoot.left, pRoot.right);
}
private boolean subIsSymmetrical(TreeNode left, TreeNode right){
if (left == null && right == null){
return true;
}
if(left != null && right != null){
return left.val == right.val && subIsSymmetrical(left.left, right.right) && subIsSymmetrical(right.left, left.right);
}
return false;
}
}
62.二叉搜索树的第k个结点
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
private TreeNode res;
private int k;
TreeNode KthNode(TreeNode pRoot, int k)
{
this.k = k;
if(k==0){
return null;
}
sub(pRoot);
return res;
}
void sub(TreeNode node){
if(node == null || k<1){
return;
}
sub(node.left);
if(k==1){
res = node;
}
k--;
sub(node.right);
return;
}
}
打印二叉树
22.从上往下打印二叉树
import java.util.*;
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
ArrayList<Integer> res = new ArrayList<>();
if(root == null){
return res;
}
LinkedList<TreeNode> q = new LinkedList<>();
q.add(root);
while(!q.isEmpty()){
TreeNode node = q.remove();
res.add(node.val);
if(node.left != null){
q.add(node.left);
}
if(node.right != null){
q.add(node.right);
}
}
return res;
}
}
59.按之字形顺序打印二叉树
import java.util.*;
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
ArrayList<ArrayList<Integer>> res = new ArrayList<ArrayList<Integer>>();
if(pRoot == null){
return res;
}
Stack<TreeNode> s1 = new Stack<>();
Stack<TreeNode> s2 = new Stack<>();
s1.push(pRoot);
while(!s1.isEmpty() || !s2.isEmpty()){
ArrayList<Integer> list = new ArrayList<>();
if(!s1.isEmpty()){
while(!s1.isEmpty()){
TreeNode node = s1.pop();
list.add(node.val);
if(node.left != null){
s2.push(node.left);
}
if(node.right != null){
s2.push(node.right);
}
}
res.add(list);
}else {
while(!s2.isEmpty()){
TreeNode node = s2.pop();
list.add(node.val);
if(node.right != null){
s1.push(node.right);
}
if(node.left != null){
s1.push(node.left);
}
}
res.add(list);
}
}
return res;
}
}
60.把二叉树打印成多行
import java.util.*;
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
ArrayList<ArrayList<Integer>> res = new ArrayList<ArrayList<Integer>>();
if(pRoot == null){
return res;
}
LinkedList<TreeNode> q = new LinkedList<>();
q.add(pRoot);
int total = 1;
int cnt = 0;
ArrayList<Integer> list = new ArrayList<>();
while(!q.isEmpty()){
TreeNode node = q.remove();
list.add(node.val);
cnt++;
if(node.left != null){
q.add(node.left);
}
if(node.right != null){
q.add(node.right);
}
if(cnt == total){
cnt = 0;
total = q.size();
res.add(new ArrayList(list));
list.clear();
}
}
return res;
}
}
数组相关
剑指offer
1.二维数组中的查找
public class Solution {
public boolean Find(int target, int [][] array) {
if(array == null){
return false;
}
int i = 0;
int j = array[0].length-1;
while(i<array.length && j>=0){
if(array[i][j] == target){
return true;
}
if(array[i][j]>target){
j--;
}else{
i++;
}
}
return false;
}
}
public class Solution {
public boolean Find(int target, int [][] array) {
if(array.length == 0 || array[0].length == 0){
return false;
}
int i=array.length-1;
int j=0;
while(array[i][j] != target){
if(array[i][j]<target){
j++;
}else if(array[i][j]>target){
i--;
}
if(i<0 || j>array[0].length-1){
return false;
}
}
return true;
}
}
13.调整数组顺序使奇数位于偶数前面
冒泡法思路,时间复杂度为O(n*n), 空间复杂的为O(1)
public class Solution {
public void reOrderArray(int [] array) {
int len = array.length;
for(int i=len-1; i>=0; i--){
for(int j=0; j<i; j++){
if(array[j]%2==0 && array[j+1]%2==1){
int temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
}
}
}
}
}
双指针法
public class Solution {
public void reOrderArray(int [] array) {
int len = array.length;
int odd = -1;
int cur = 0;
for(cur = 0; cur<len; cur++){
if(array[cur]%2 == 1){
int temp = array[cur];
for(int k=cur; k>odd+1; k--){
array[k] = array[k-1];
}
array[odd+1] = temp;
odd++;
}
}
}
}
使用一个辅助数组,时间复杂度为O(n), 空间复杂度为O(n), 以空间换时间
public class Solution {
public void reOrderArray(int [] array) {
int oddNum = 0;
for(int i=0; i<array.length; i++){
if(array[i]%2 == 1){
oddNum++;
}
}
int[] arr = array.clone();
int odd=0;
int even = oddNum;
for(int e : arr){
if(e%2 == 1){
array[odd] = e;
odd++;
}else{
array[even] = e;
even++;
}
}
}
}
28.数组中出现次数超过一半的数字
多数投票问题(摩尔投票问题),使用 Boyer-Moore Majority Vote Algorithm 来解决这个问题,使得时间复杂度为 O(n), 空间复杂的为O(1)
若数组中存在出现次数超过一半的数字,则major最终记录的一定是这个数
public class Solution {
public int MoreThanHalfNum_Solution(int [] array) {
int major = array[0];
int cnt = 1;
for(int i=1; i<array.length; i++){
if(array[i] == major){
cnt++;
}else{
cnt--;
if(cnt == 0){
major = array[i];
cnt=1;
}
}
}
int n = 0;
for(int e : array){
if(e == major){
n++;
}
}
return n>array.length/2? major:0;
}
}
使用一个额外的辅助HashMap, 时间复杂度为O(n), 但空间复杂度也为O(n)
import java.util.*;
public class Solution {
public int MoreThanHalfNum_Solution(int [] array) {
HashMap<Integer, Integer> map = new HashMap<>();
for(int i=0; i<array.length; i++){
if(map.containsKey(array[i])){
map.put(array[i], map.get(array[i])+1);
}else{
map.put(array[i], 1);
}
if(map.get(array[i])>array.length/2){
return array[i];
}
}
return 0;
}
}
29.最小的K个数
使用最大优先队列维护前k个最小元素
import java.util.*;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer> res = new ArrayList<>();
if(input.length < k){
return res;
}
PriorityQueue<Integer> pq = new PriorityQueue<>(new Comparator<Integer>(){
public int compare(Integer a, Integer b){
return b-a;
}
});
for(int i=0; i<input.length; i++){
pq.add(input[i]);
if(pq.size()>k){
pq.remove();
}
}
for(int e: pq){
res.add(e);
}
return res;
}
}
32.把数组排成最小的数
可以看成是一个排序问题,在比较两个字符串 S1 和 S2 的大小时,应该比较的是 S1+S2 和 S2+S1 的大小,如果 S1+S2 < S2+S1,那么应该把 S1 排在前面,否则应该把 S2 排在前面。
import java.util.*;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer> res = new ArrayList<>();
if(input.length < k){
return res;
}
PriorityQueue<Integer> pq = new PriorityQueue<>(new Comparator<Integer>(){
public int compare(Integer a, Integer b){
return b-a;
}
});
for(int i=0; i<input.length; i++){
pq.add(input[i]);
if(pq.size()>k){
pq.remove();
}
}
for(int e: pq){
res.add(e);
}
return res;
}
}
35.数组中的逆序对
归并过程
public class Solution {
private int res=0;
public int InversePairs(int [] array) {
merge(array, 0, array.length-1);
return res;
}
private void merge(int [] arr, int l, int r){
if(l>=r){
return;
}
int mid = (r-l)/2+l;
merge(arr, l, mid);
merge(arr, mid+1, r);
submerge(arr,l,mid,r);
}
private void submerge(int [] arr, int l, int mid, int r){
int [] temp = new int[r-l+1];
for(int i=l; i<=r; i++){
temp[i-l] = arr[i];
}
int m=l;
int n=mid+1;
for(int i=l; i<=r; i++){
if(m>mid){
arr[i] = temp[n-l];
n++;
}else if(n>r){
arr[i] = temp[m-l];
m++;
}else{
if(temp[m-l]<temp[n-l]){
arr[i] = temp[m-l];
m++;
}else{
arr[i] = temp[n-l];
n++;
res+=mid-m+1;
res%=1000000007;
}
}
}
}
}
41.和为S的连续正数序列
滑动窗口法
import java.util.ArrayList;
public class Solution {
public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
ArrayList<ArrayList<Integer>> res = new ArrayList<ArrayList<Integer>>();
int l = 1, r = 2;
while(l<r){
int curSum = (l+r)*(r-l+1)/2;
if(curSum == sum){
ArrayList<Integer> list = new ArrayList<>();
for(int i=l; i<=r; i++){
list.add(i);
}
res.add(list);
l++;
}else if(curSum < sum){
r++;
}else{
l++;
}
}
return res;
}
}
42.和为S的两个数字
对撞指针法,距离越远的两个数,积越小
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
ArrayList<Integer> list = new ArrayList<>();
int l = 0, r = array.length-1;
while(l<r){
if(array[l] + array[r] < sum){
l++;
}else if(array[l] + array[r] > sum){
r--;
}else{
list.add(array[l]);
list.add(array[r]);
return list;
}
}
return list;
}
}
50.数组中重复的数字
要求时间复杂度 O(N),空间复杂度 O(1)。因此不能使用排序的方法,也不能使用额外的标记数组。
对于这种数组元素在 [0, n-1] 范围内的问题,可以将值为 i 的元素调整到第 i 个位置上进行求解。
以 (2, 3, 1, 0, 2, 5) 为例,遍历到位置 4 时,该位置上的数为 2,但是第 2 个位置上已经有一个 2 的值了,因此可以知道 2 重复:
public class Solution {
// Parameters:
// numbers: an array of integers
// length: the length of array numbers
// duplication: (Output) the duplicated number in the array number,length of duplication array is 1,so using duplication[0] = ? in implementation;
// Here duplication like pointor in C/C++, duplication[0] equal *duplication in C/C++
// 这里要特别注意~返回任意重复的一个,赋值duplication[0]
// Return value: true if the input is valid, and there are some duplications in the array number
// otherwise false
public boolean duplicate(int numbers[],int length,int [] duplication) {
if(length < 2 || numbers == null){
return false;
}
for(int i=0; i<numbers.length; i++){
while(numbers[i] != i){
if(numbers[i] == numbers[numbers[i]]){
duplication[0] = numbers[i];
return true;
}
// 交换numbers[i] 和·numbers[numbers[i]]
int temp = numbers[i];
numbers[i] = numbers[numbers[i]];
numbers[temp] = temp;
}
}
return false;
}
}
51.构建乘积数组
分解成上下三角形处理
import java.util.ArrayList;
public class Solution {
public int[] multiply(int[] A) {
if(A.length<=1){
return null;
}
int[] B = new int[A.length];
B[0] = 1;
for(int i=1; i<B.length; i++){
B[i] = B[i-1]*A[i-1];
}
int temp = 1;
for(int i=B.length-1; i>=0; i--){
B[i] = B[i]*temp;
temp = temp*A[i];
}
return B;
}
}
二分查找
在有序的数组中,考虑二分查找
6.旋转数组的最小数字
注意:数组中只剩两个数时,mid=l+(r-l)时, mid == l,
通过array[mid] 与 array[l] 比较,当array[mid] == array[l]时,需要额外考虑 mid==i 的情况
并且,l 指向的位置不可越过最小值处,比较复杂
import java.util.ArrayList;
public class Solution {
public int minNumberInRotateArray(int [] array) {
if(array.length == 0){
return 0;
}
int l = 0;
int r = array.length-1;
int mid = l;
while(l<=r){
mid = l+(r-l)/2;
if(array[mid] == array[l] && array[mid] == array[r]){
return sub(array, l, r);
}
if(array[mid]>array[l]){
l = mid;
}else if(array[mid]<array[l]){
r = mid;
}else{
if(mid != l){
l = mid ;
}else{
// 只剩下两个数的时候
mid = array[l]<array[r]? l:r;
break;
}
}
}
return array[mid];
}
private int sub(int[] arr, int l, int r){
int res = arr[l];
for(int i=l+1; i<=r; i++){
if(arr[i] < arr[i-1]){
return arr[i];
}
}
return res;
}
}
通过array[mid] 与 array[l] 比较, 简单许多
import java.util.ArrayList;
public class Solution {
public int minNumberInRotateArray(int [] array) {
if(array.length == 0){
return 0;
}
int l = 0;
int r = array.length-1;
while(l<r){
int mid = l+(r-l)/2;
if(array[mid] == array[l] && array[mid] == array[r]){
return sub(array, l, r);
}
if(array[mid]<=array[r]){
r = mid;
}else{
l = mid+1;
}
}
return array[l];
}
private int sub(int[] arr, int l, int r){
int res = arr[l];
for(int i=l+1; i<=r; i++){
if(arr[i] < arr[i-1]){
return arr[i];
}
}
return res;
}
}
37.数字在排序数组中出现的次数
二分搜索的变形
public class Solution {
public int GetNumberOfK(int [] array , int k) {
int start = binary(array, k);
int end = binary(array, k+1);
// 当k大于array中的最大值时,start=array.length ,此时使用array[start]会越界
// 当k小于array中的最小值时,start=0
// 当k在array的范围中但在array中不存在时,start=array中大于k的第一个元素的位置
if(start == array.length || array[start] != k){
return 0;
}
return end-start;
}
// 寻找第一个大于等于k的元素
// k大于arr中的最大值时,最终l=r=arr.length
// k小于arr中的最小值时,最终l=r=0
private int binary(int[] arr, int k){
int l=0, r=arr.length;
while(l<r){
int mid = l+(r-l)/2;
if(k>arr[mid]){
l = mid+1;
}else{
r=mid;
}
}
return l;
}
}
使用二分查找的变形,查找第一个和最后一个重复的元素
public class Solution {
public int GetNumberOfK(int [] array , int k) {
int l = getFirstK(array, k);
int r = getLastK(array, k);
if(l == -1 || r == -1){
return 0;
}
return r-l+1;
}
private int getFirstK(int[] arr, int k){
int l = 0;
int r = arr.length-1;
while(l<=r){
int mid = (r-l)/2+l;
if(k < arr[mid]){
r = mid - 1;
}else if(k > arr[mid]){
l = mid + 1;
}else if(mid-1 >= 0 && arr[mid-1] == k){
r = mid -1 ;
}else{
return mid;
}
}
return -1;
}
private int getLastK(int[] arr,int k){
int l = 0;
int r = arr.length - 1;
while(l<=r){
int mid = (r-l)/2 + l;
if(arr[mid] < k){
l = mid + 1;
}else if(arr[mid] > k){
r = mid -1;
}else if(mid+1 <= r && arr[mid+1] == k){
l = mid + 1;
}else{
return mid;
}
}
return -1;
}
}
字符串操作
剑指offer
2.替换空格
额外使用一个StringBuilder, 空间复杂度为O(n)
public class Solution {
public String replaceSpace(StringBuffer str) {
String s = str.toString();
StringBuilder sb = new StringBuilder();
for(int i=0; i<s.length(); i++){
char c = s.charAt(i);
if(c == ' '){
sb.append("%20");
}else{
sb.append(String.valueOf(c));
}
}
return sb.toString();
}
}
直接在原string上操作,空间复杂度为O(1)
public class Solution {
public String replaceSpace(StringBuffer str) {
int p1 = str.length()-1; // 初始字符串的尾部
// 一个空格占一个字符,要将空格替换为%20这样的三个字符,所以每遇到一个空格,先在末尾扩充两个字符
for(int i=0; i<=p1; i++){
if(str.charAt(i) == ' '){
str.append(" "); //
}
}
int p2 = str.length()-1; // 扩充后字符串的尾部
while(p1>=0 && p1<p2){
char c = str.charAt(p1--);
if(c == ' '){
str.setCharAt(p2--,'0');
str.setCharAt(p2--,'2');
str.setCharAt(p2--,'%');
}else{
str.setCharAt(p2--,c);
}
}
return str.toString();
}
}
27.字符串的排列
34.第一个只出现一次的字符位置
此题的考点是如何节省空间
最直观的解法是使用 HashMap 对出现次数进行统计。但是考虑到要统计的字符范围有限(一个英文字符只占一个Byte,使用256个空间即可记录所有英文字符),因此可以使用整型数组代替 HashMap,从而将空间复杂度由 O(N) 降低为 O(1)。
public class Solution {
public int FirstNotRepeatingChar(String str) {
int[] freq = new int[256];
for(int i=0; i<str.length(); i++){
char c = str.charAt(i);
freq[c]++;
}
for(int i=0; i<str.length(); i++){
char c = str.charAt(i);
if(freq[c]==1){
return i;
}
}
return -1;
}
}
以上实现的空间复杂度还不是最优的。考虑到只需要找到只出现一次的字符,那么需要统计的次数信息只有 0,1,更大,使用两个比特位就能存储这些信息。
import java.util.*;
public class Solution {
public int FirstNotRepeatingChar(String str) {
BitSet bs0 = new BitSet(256);
BitSet bs1 = new BitSet(256);
for(int i=0; i<str.length(); i++){
char c = str.charAt(i);
if(!bs0.get(c) && !bs1.get(c)){
bs0.set(c); // 0 0 ===> 0 1 表示第c位的字符第一次出现
}else if(bs0.get(c) && !bs1.get(c)){
bs1.set(c); // 0 1 ===> 1 1 表示第c位的字符第二次出现
}
}
for(int i=0; i<str.length(); i++){
char c = str.charAt(i);
if(bs0.get(c) && !bs1.get(c)){
return i;
}
}
return -1;
}
}
49.把字符串转换成整数
52.正则表达式匹配
53.表示数值的字符串
54.字符流中第一个不重复的字符
旋转字符串
要求在不使用额外空间的条件下对字符串进行旋转操作
可以通过多次局部翻转结合全部翻转完成最终的旋转
43.左旋转字符串
字符串拼接
public class Solution {
public String LeftRotateString(String str,int n) {
if(str.length() == 0){
return "";
}
//n = n%str.length();
String res = "";
res = str.substring(n,str.length()) + str.substring(0,n);
return res;
}
}
翻转法
例,先将 “abc” 和 “XYZdef” 分别翻转,得到 “cbafedZYX”,然后再把整个字符串翻转得到 “XYZdefabc”。
public class Solution {
public String LeftRotateString(String str,int n) {
if(n>=str.length()){
return str;
}
char[] cs = str.toCharArray();
reverse(cs, 0, n-1);
reverse(cs, n, cs.length-1);
reverse(cs, 0, cs.length-1);
return String.valueOf(cs);
}
// 对数组arr中范围为[l,r]的元素进行翻转
private void reverse(char[] arr, int l, int r){
while(l<r){
char temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
l++;
r--;
}
}
}
44.翻转单词顺序列
转为String数组直接处理
public class Solution {
public String ReverseSentence(String str) {
if(str == null){
return "";
}
String[] ss = str.split("\\s+");
// 防止str=" " 的情况
if(ss.length == 0){
return str;
}
String res = "";
for(int i=ss.length-1; i>=0; i--){
res += ss[i];
if(i != 0){
res += " ";
}
}
return res;
}
}
题目应该有一个隐含条件,就是不能用额外的空间。虽然 Java 的题目输入参数为 String 类型,需要先创建一个字符数组使得空间复杂度为 O(N),但是正确的参数类型应该和原书一样,为字符数组,并且只能使用该字符数组的空间。任何使用了额外空间的解法在面试时都会大打折扣,包括递归解法。
正确的解法应该是和书上一样,先旋转每个单词,再旋转整个字符串。
public class Solution {
public String ReverseSentence(String str) {
char[] cs = str.toCharArray();
int n = str.length();
int i=0;
for(int j=0; j<=n; j++){
if(j==n || cs[j] == ' '){
reverse(cs, i, j-1);
i=j+1;
}
}
reverse(cs,0,n-1);
return String.valueOf(cs);
}
private void reverse (char[] arr, int l, int r){
while(l<r){
char temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
l++;
r--;
}
}
}
回溯
剑指offer
62. 矩阵中的路径
public class Solution {
private boolean[][] used;
private int m, n;
private int[][] dist = {{0,1}, {0,-1}, {1,0}, {-1,0}};
public boolean hasPath(char[] matrix, int rows, int cols, char[] str){
// 构建字符矩阵
char[][] grid = new char[rows][cols];
for(int i=0; i<matrix.length; i++){
grid[i/cols][i%cols] = matrix[i];
}
m = rows;
n = cols;
used = new boolean[rows][cols];
for(int i=0; i<m; i++){
for(int j=0; j<n; j++){
if(bt(grid, i, j, str, 0)){
return true;
}
}
}
return false;
}
// 边界检查
private boolean inArea(int i, int j){
return i>=0 && i<m && j>=0 && j<n;
}
// 回溯算法
private boolean bt(char[][] grid, int i, int j, char[] str, int index){
// 注意,此处递归到底的回溯条件,两个判断不可反
if(grid[i][j] != str[index]){
return false;
}
if(index == str.length-1){ // 此处是str.length-1 而不是str.length ; case:"AAAAAAAAAAAA",3,4,"AAAAAAAAAAAA"时, 当index=str.length时,返回给上层的可能是false
return true;
}
used[i][j] = true;
for(int d=0; d<4; d++){
int ni = i+dist[d][0];
int nj = j+dist[d][1];
if(inArea(ni, nj) && !used[ni][nj] && bt(grid, ni, nj, str, index+1)){
return true;
}
}
// 回溯
used[i][j] = false;
return false;
}
}
DFS
剑指offer
63. 机器人的运动范围
public class Solution {
private int[][] dire = {{0,1}, {0,-1}, {1,0}, {-1,0}};
private boolean[][] used;
private int m, n;
private int res;
public int movingCount(int threshold, int rows, int cols){
m = rows;
n = cols;
used = new boolean[m][n];
// 构建矩阵
int[][] grid = new int[m][n];
for(int i=0; i<m; i++){
for(int j=0; j<n; j++){
grid[i][j] = getSum(i, j);
}
}
dfs(grid, 0, 0, threshold);
return res;
}
// 返回坐标位数之和
private int getSum(int i, int j){
int sum = 0;
while(i>0 || j>0){
sum += i%10;
sum += j%10;
i /= 10;
j /= 10;
}
return sum;
}
// 边界检查
private boolean inArea(int i, int j){
return i>=0 && i<m && j>=0 && j<n;
}
private void dfs(int[][] grid, int i, int j, int thre){
if(grid[i][j]>thre){
return;
}
used[i][j] = true;
res++;
for(int d=0; d<4; d++){
int ni = i+dire[d][0];
int nj = j+dire[d][1];
if(inArea(ni, nj) && !used[ni][nj]){
dfs(grid, ni, nj, thre);
}
}
}
}