1 二叉树
1.1 二叉树层次遍历模板
1.1.1 若不需要确定遍历到那一层了:
伪代码,不仅可用于二叉树,可针对所有用BFS解题。
void bfs(){
vis[]={0};
queue<int> pq(start_val);
while(!pq.empty()){
int cur =pq.pop();
for(遍历cur所有的相邻节点next){
if(next节点有效&&vis[next]=0){
vis[next]=1;
pq.push(next);
}
}
}
}
1.1.2 若需要确定到哪一层了
void bfs(){
int level=0;
vis[]={0};
queue<int> pq(start_val);
while(!pq.empty()){
int size=pq.size();
while(size--){
int cur =pq.pop();
for(遍历cur所有的相邻节点next){
if(next节点有效&&vis[next]=0){
vis[next]=1;
pq.push(next);
}//end for
}//end inner while
level++;
}
}
}
1.2 按之字形顺序打印二叉树
套模板:
import java.util.ArrayList;
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) {
Deque<TreeNode> q = new LinkedList();
ArrayList<ArrayList<Integer>> lists=new ArrayList<ArrayList<Integer>>();
if(pRoot==null) return lists;
int level=0;
q.add(pRoot);
while(!q.isEmpty()){
int size=q.size();
ArrayList<Integer> list=new ArrayList<>();
while(size--!=0){
TreeNode curNode=q.poll();
list.add(curNode.val);
if(curNode.left!=null){
q.add(curNode.left);
}
if(curNode.right!=null){
q.add(curNode.right);
}
}
level++;
if(level%2==0){
Collections.reverse(list);
}
lists.add(list);
}
return lists;
}
}
1.3 二叉搜索树的第k个结点
二叉树的中序遍历出来就是递增序列
递归:
public class Solution {
TreeNode node=null;
int count=0;
void dfs(TreeNode pRoot,int k){//中序遍历
if(count<k&&pRoot.left!=null){
dfs(pRoot.left,k);
}
if(++count==k) {
node=pRoot;
return;
}
if(count<k&&pRoot.right!=null){
dfs(pRoot.right,k);
}
}
TreeNode KthNode(TreeNode pRoot, int k) {
if(pRoot==null||k==0)return null;
dfs(pRoot,k);
return node;
}
}
非递归:
import java.util.*;
public class Solution {
TreeNode KthNode(TreeNode pRoot, int k) {
if(pRoot==null||k==0)return null;
int cnt=0;
Deque<TreeNode> stack=new LinkedList<>();
while(pRoot!=null||!stack.isEmpty()){
while(pRoot!=null){
stack.push(pRoot);
pRoot=pRoot.left;
}
pRoot=stack.pop();
cnt++;
if(cnt==k) return pRoot;
pRoot=pRoot.right;
}
return null;
}
}
1.4 重建二叉树
分析
根据中序遍历和前序遍历可以确定二叉树,具体过程为:
根据前序序列第一个结点确定根结点
根据根结点在中序序列中的位置分割出左右两个子序列
对左子树和右子树分别递归使用同样的方法继续分解
例如:
前序序列{1,2,4,7,3,5,6,8} = pre
中序序列{4,7,2,1,5,3,8,6} = in
根据当前前序序列的第一个结点确定根结点,为 1
找到 1 在中序遍历序列中的位置,为 in[3]
切割左右子树,则 in[3] 前面的为左子树, in[3] 后面的为右子树
则切割后的左子树前序序列为:{2,4,7},切割后的左子树中序序列为:{4,7,2};切割后的右子树前序序列为:{3,5,6,8},切割后的右子树中序序列为:{5,3,8,6}
对子树分别使用同样的方法分解
/**
* 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 [] vin) {
if(pre.length==0||vin.length==0) return null;
TreeNode root=new TreeNode(pre[0]);
//从中序中找到前序的根
for(int i=0;i<vin.length;i++){
if(vin[i]==pre[0]){
// 左子树,注意 copyOfRange 函数,左闭右开
root.left=reConstructBinaryTree(Arrays.copyOfRange(pre,1,i+1),Arrays.copyOfRange(vin,0,i));
// 左子树,注意 copyOfRange 函数,左闭右开
root.right=reConstructBinaryTree(Arrays.copyOfRange(pre,i+1,pre.length),Arrays.copyOfRange(vin,i+1,vin.length));
break;
}
}
return root;
}
}
树的子结构
**思想:**先遍历大树,找到相同的根节点再判断是否为子树
/**
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(root2==null||root1==null)return false;
return isSubtree(root1,root2)||HasSubtree(root1.left,root2)||HasSubtree(root1.right,root2);
}
public boolean isSubtree(TreeNode root,TreeNode sub){
if(sub==null) return true;
if(root==null) return false;
return isSubtree(root.left,sub.left)&isSubtree(root.right,sub.right)&(root.val==sub.val);
}
}
1.5 二叉搜索树的后序遍历序列
import java.util.*;
public class Solution {
public boolean VerifySquenceOfBST(int [] sequence) {
if(sequence==null||sequence.length==0) return false;
return isBackSort(sequence,0,sequence.length-1);
}
public boolean isBackSort(int[] sequence,int start,int end){
if(start >= end)return true;
//最后一个是根节点
int root=sequence[end];
//po为左右子树分界点
int po=start;
while(sequence[po]<root)
po++;
//右子树若小于root,则返回false
for(int i=po;i<end;i++){
if(sequence[i]<root) return false;
}
return isBackSort(sequence,start,po-1)&&isBackSort(sequence,po,end-1);
}
}
1.6 二叉树中和为某一值的路径(二)
很自然想到回溯算法
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>> lists=new ArrayList<>();
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int expectNumber) {
if(root==null)return lists;
ArrayList<Integer> list=new ArrayList<>();
getPath(root,list,expectNumber);
return lists;
}
public void getPath(TreeNode root,ArrayList list,int tar){
if(root==null) return;
list.add(root.val);
if(root.val==tar&&root.left==null&&root.right==null){
//注意这里如果直接多次添加list只会记录最后一次,因为lists中添加的都是list的引用
lists.add(new ArrayList<>(list));
list.remove(list.size()-1);
return;
}
getPath(root.left,list,tar-root.val);
getPath(root.right,list,tar-root.val);
list.remove(list.size()-1);
}
}
1.7 二叉搜索树与双向链表
很容易想到中序遍历是从小到大遍历的,于是有:
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
import java.util.*;
public class Solution {
TreeNode pre=null;
TreeNode re=null;
public TreeNode Convert(TreeNode pRootOfTree) {
if(pRootOfTree==null) return null;
Convert(pRootOfTree.left);
if (pre!= null){
pRootOfTree.left=pre;
pre.right=pRootOfTree;
}else{
re=pRootOfTree;
}
pre=pRootOfTree;
Convert(pRootOfTree.right);
return re;
}
}
但这时,我们需要一个re来记录链头,于是可以想到,如果先遍历右子树再遍历左子树就可以直接返回pre了
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
import java.util.*;
public class Solution {
TreeNode pre=null;
public TreeNode Convert(TreeNode pRootOfTree) {
if(pRootOfTree==null) return null;
Convert(pRootOfTree.right);
if (pre!= null){
pRootOfTree.right=pre;
pre.left=pRootOfTree;
}
pre=pRootOfTree;
Convert(pRootOfTree.left);
return pre;
}
}
1.8 判断是不是平衡二叉树
思路:从底向上依次判断,子树平衡,整体就不平衡
public class Solution {
public boolean IsBalanced_Solution(TreeNode root) {
return getDepth(root)!=-1;
}
public int getDepth(TreeNode root){
int left=0,right=0;
if(root==null) return 0;
else{
left=getDepth(root.left);
if(left==-1) return -1;
right=getDepth(root.right);
if(right==-1) return -1;
}
return Math.abs(left-right)>1?-1:Math.max(left,right)+1;
}
}
1.9 二叉树的下一个结点
思路:
- 直接将中序遍历存进一个list
- 分类:
- 该节点有右子树,那么下一个节点就是其右子树中最左的节点
- 该节点没有右子树
- 该节点是其父节点的左子树,那么他的下一节点就是其父子树
- 该节点是其父节点的右子树,那么一直向上搜索,直到找到一个节点的左子树节点是他的下一节点的下一节点。。。
实现:
/*
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;
}else{
pNode=pNode.next;
}
}
return null;
}
}
1.10 对称的二叉树
思路:先对比两个节点本身,然后递归对比左节点的节点约右节点的右节点,还有左节点的右节点和右节点的左节点
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
import java.util.*;
public class Solution {
boolean isSymmetrical(TreeNode pRoot) {
if(pRoot==null) return true;
return method(pRoot.left,pRoot.right);
}
boolean method(TreeNode left,TreeNode right){
if(left==null&&right==null)return true;
if(left==null||right==null) return false;
if(left.val!=right.val) return false;
return method(left.left,right.right)&&method(left.right,right.left);
}
}
1.11 把二叉树打印成多行
思路:标准的层次遍历
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> > lists=new ArrayList<>();
if(pRoot==null)return lists;
Deque<TreeNode> queue=new LinkedList<>();
queue.add(pRoot);
while(queue.size()!=0){
int curLevelNum=queue.size();
ArrayList<Integer> list=new ArrayList<>();
for(int i=0;i<curLevelNum;i++){
TreeNode node=queue.poll();
list.add(node.val);
if(node.left!=null) queue.add(node.left);
if(node.right!=null) queue.add(node.right);
}
lists.add(list);
}
return lists;
}
}
1.12 序列化二叉树
思路:
序列化:这道题可以使用递归或者栈或者队列,当节点不为空,向字符串中添加“val”并用逗号“,”隔开。若为空则添加“#”,并用逗号“,”隔开
反序列化,跟着序列化的顺序反推
代码如下
递归:
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
import java.util.*;
public class Solution {
String Serialize(TreeNode root) {
if (root == null) return "";
return helpSerialize(root, new StringBuilder()).toString();
}
private StringBuilder helpSerialize(TreeNode root, StringBuilder s) {
if (root == null) return s;
s.append(root.val).append("!");
if (root.left != null) {
helpSerialize(root.left, s);
} else {
s.append("#!"); // 为null的话直接添加即可
}
if (root.right != null) {
helpSerialize(root.right, s);
} else {
s.append("#!");
}
return s;
}
private int index = 0; // 设置全局主要是遇到了#号的时候需要直接前进并返回null
TreeNode Deserialize(String str) {
if (str == null || str.length() == 0) return null;
String[] split = str.split("!");
return helpDeserialize(split);
}
private TreeNode helpDeserialize(String[] strings){
if(strings[index].equals("#")){
index++;//判断下一个节点
return null;
}
//使用当前值作为新节点
TreeNode root=new TreeNode(Integer.valueOf(strings[index++]));
root.left=helpDeserialize(strings);
root.right=helpDeserialize(strings);
return root;
}
}
使用队列:
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
import java.util.*;
public class Solution {
String Serialize(TreeNode root) {
if (root == null) return "";
StringBuilder sb=new StringBuilder("");
Deque<TreeNode> queue=new LinkedList<>();
//加入根节点
queue.add(root);
sb.append(String.valueOf(root.val)+",");
while(!queue.isEmpty()){
TreeNode curNode=queue.poll();
if(curNode.left==null){
sb.append("#,");
}else{
sb.append(String.valueOf(curNode.left.val)+",");
queue.add(curNode.left);
}
if(curNode.right==null){
sb.append("#,");
}else{
sb.append(String.valueOf(curNode.right.val)+",");
queue.add(curNode.right);
}
}
sb.delete(sb.length(),sb.length());
return sb.toString();
}
TreeNode Deserialize(String str) {
if (str == null || str.length() == 0) return null;
String[] splits = str.split(",");
//获得根节点
TreeNode root=new TreeNode(Integer.parseInt(splits[0]));
Deque<TreeNode> queue=new LinkedList<>();
queue.add(root);
int index=1;
while(!queue.isEmpty()&&index<splits.length){
TreeNode curNode=queue.poll();
//左
if(splits[index].equals("#")){
index++;
}else{
TreeNode left=new TreeNode(Integer.parseInt(splits[index++]));
queue.add(left);
curNode.left=left;
}
//右
if(splits[index].equals("#")){
index++;
}else{
TreeNode right=new TreeNode(Integer.parseInt(splits[index++]));
queue.add(right);
curNode.right=right;
}
}
return root;
}
}
1.13 二叉树中和为某一值的路径(三)
思路:因为首尾节点不一定是根节点和叶子节点,所以要从每个叶子节点开始向下进行遍历。
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 {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param root TreeNode类
* @param sum int整型
* @return int整型
*/
private int num=0;
public int FindPath (TreeNode root, int sum) {
// write code here
if(root==null) return num;
//因为起始节点可以不是根节点,所以从每个子节点开始的路径都要搜索
dfs(root,sum);
FindPath(root.left,sum);
FindPath(root.right,sum);
return num;
}
public void dfs(TreeNode root,int sum){
sum-=root.val;
//匹配则计数+1
if(sum==0) num++;
//左子树非空,继续搜索
if(root.left!=null) dfs(root.left,sum);
//右子树非空,继续搜索
if(root.right!=null) dfs(root.right,sum);
}
}
1.14 在二叉树中找到两个节点的最近公共祖先
思路:
- 可以层次遍历直到两个节点都被遍历到了,然后将一个节点到根节点的路径存到list中,最后在用宁外一个节点的父节点一次匹配list
- 注释表明
import java.util.*;
/*
* public class TreeNode {
* int val = 0;
* TreeNode left = null;
* TreeNode right = null;
* }
*/
public class Solution {
/**
*
* @param root TreeNode类
* @param o1 int整型
* @param o2 int整型
* @return int整型
*/
public int lowestCommonAncestor(TreeNode root, int o1, int o2) {
return helper(root, o1, o2).val;
}
public TreeNode helper(TreeNode root, int o1, int o2) {
if (root == null || root.val == o1 || root.val == o2)
return root;
TreeNode left = helper(root.left, o1, o2);
TreeNode right = helper(root.right, o1, o2);
//如果left为空,说明这两个节点在root结点的右子树上,我们只需要返回右子树查找的结果即可
if (left == null)
return right;
//同上
if (right == null)
return left;
//如果left和right都不为空,说明这两个节点一个在root的左子树上一个在root的右子树上,
//我们只需要返回cur结点即可。
return root;
}
}
1.15 二叉搜索树的最近公共祖先
思路:在两个节点之上,节点值处于两个节点中间的,只有他们的父节点,所以当前值大于两个目标节点时,说明他们的最近公共节点在当前节点的左子树,小于两个目标节点时,说明他们的最近公共节点在当前节点的右子树。
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 {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param root TreeNode类
* @param p int整型
* @param q int整型
* @return int整型
*/
public int lowestCommonAncestor (TreeNode root, int p, int q) {
// write code here
TreeNode curNode=root;
while(true){
if(curNode.val<p&&curNode.val<q){
curNode=curNode.right;
}else if(curNode.val>p&&curNode.val>q){
curNode=curNode.left;
}else{
break;//在两个节点之上,节点值处于两个节点中间的,只有他们的父节点
}
}
return curNode.val;
}
}
2 队列
2.1 用两个栈实现队列
思路:一个栈用作进,一个栈用作出。出栈为空时把进栈中所有元素push进出栈,然后每次pop就直接从出栈pop
import java.util.Stack;
public class Solution {
Stack<Integer> stack1 = new Stack<Integer>();//入栈
Stack<Integer> stack2 = new Stack<Integer>();//出栈
public void push(int node) {
stack1.push(node);
}
public int pop() {
if(stack2.isEmpty()){
while(!stack1.isEmpty()){
stack2.push(stack1.pop());
}
}
return stack2.pop();
}
}
2.2 包含min函数的栈
思路
传统方法:使用双栈,使用第二个栈存储当前最小值
class Solution {
public:
stack<int> normal, minval;
void push(int value) {
normal.push(value);
if (minval.empty()) {
minval.push(value);
}
else {
if (value <= minval.top()) {
minval.push(value);
}
else {
minval.push(minval.top());
}
}
}
void pop() {
normal.pop();
minval.pop();
}
int top() {
return normal.top();
}
int min() {
return minval.top();
}
};
方法2:当当前值比最小值还小时,先多push一次上一个最小值,当pop的值为当前最小值时,再多pop一次获得上一个最小值
import java.util.*;
public class Solution {
Stack<Integer> stack=new Stack<>();
int min=Integer.MAX_VALUE;
public void push(int node) {
if(stack.isEmpty()){
min=node;
stack.push(node);
}else{
if(node<=min){
stack.push(min);//多push一次上一个最小值
stack.push(node);
min=node;
}else{
stack.push(node);
}
}
}
public void pop() {
if(stack.isEmpty())return;
int num=stack.pop();
if(num==min){
min=stack.pop();//更新min为上一个最小值
}
}
public int top() {
return stack.peek();
}
public int min() {
return min;
}
}
2.3 滑动窗口的最大值
思路:使用单调队列:假设这里有那么一个容器可以保留上述操作。
- 遍历数组的每一个元素,
- 如果容器为空,则直接将当前元素加入到容器中。
- 如果容器不为空,则让当前元素和容器的最后一个元素比较,如果大于,则将容器的最后一个元素删除,然后继续讲当前元素和容器的最后一个元素比较
- 如果当前元素小于容器的最后一个元素,则直接将当前元素加入到容器的末尾
- 如果容器头部的元素已经不属于当前窗口的边界,则应该将头部元素删除
那么容器头部就会保留该滑动窗口中最大的值
代码如下:
import java.util.*;
public class Solution {
public ArrayList<Integer> maxInWindows(int [] num, int size)
{
ArrayList<Integer> list = new ArrayList<>();
Deque<Integer> stack = new LinkedList<>();
if(size==0||num.length==0||size>num.length)return list;
for(int i=0;i<num.length;i++){
while(!stack.isEmpty()&&num[stack.peekFirst()]<num[i]){
stack.removeFirst();
}
stack.addFirst(i);
// 判断队列的头部的下标是否过期
if(stack.peekLast()+size<=i){
stack.removeLast();
}
// 判断是否形成了窗口(长度大于3)
if(i+1>=size){
list.add(num[stack.peekLast()]);
}
}
return list;
}
}
3 链表
3.1 从尾到头打印链表
一开始想的将链表反转,反转后在遍历,果不其然超时了
然后利用递归,但发现当数据量极大时,会造成栈溢出,代码如下:
/**
* public class ListNode {
* int val;
* ListNode next = null;
*
* ListNode(int val) {
* this.val = val;
* }
* }
*
*/
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> list=new ArrayList();
method(list,listNode);
return list;
}
public void method(ArrayList<Integer> list,ListNode listNode){
if(listNode!=null){
method(list,listNode.next);
list.add(listNode.val);
}
return;
}
}
然后尝试着直接用栈,结果通过了,代码如下:
/**
* public class ListNode {
* int val;
* ListNode next = null;
*
* ListNode(int val) {
* this.val = val;
* }
* }
*
*/
import java.util.ArrayList;
import java.util.Stack;
public class Solution {
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ListNode l=listNode;
ArrayList<Integer> arrayList=new ArrayList<>();
if(listNode==null) return arrayList;
Stack<Integer> stack=new Stack<>();
while(l.next!=null){
stack.push(l.val);
l=l.next;
}
stack.push(l.val);
while (!stack.empty()){
arrayList.add(stack.pop());
}
return arrayList;
}
}
还有使用辅助数组的方法:
/**
* public class ListNode {
* int val;
* ListNode next = null;
*
* ListNode(int val) {
* this.val = val;
* }
* }
*
*/
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> list=new ArrayList<>();
int a[]=new int[10000];
int num=0;
while(listNode!=null){
a[num++]=listNode.val;
listNode=listNode.next;
}
for(int i=num-1;i>=0;i--){
list.add(a[i]);
}
return list;
}
}
4 其他算法
4.1 和为S的连续正数序列
对于求一个区间和,一贯的优化技巧是使用前缀和。比如:
sum[i]表示前i个数的和。比如sum[1] = 1,表示前一个数的和为1,sum[2] = 3, 表示前2个数的和为3.现在我们要求区间[2,4]表示求第2,3,4个数的和,就等于sum[4] - sum[1] = 9。代码中我们用一个变量tmp来模拟这个前缀和。
代码如下:
import java.util.ArrayList;
public class Solution {
public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
ArrayList<ArrayList<Integer> >lists=new ArrayList<>();
int tmp=0;
for(int i=1;i<=sum/2;i++){
for(int j=i;j<sum;j++){
tmp+=j;
if(sum==tmp){
ArrayList<Integer> list=new ArrayList<>();
for(int k=i;k<=j;k++)
list.add(k);
lists.add(list);
}else if(tmp>sum){
tmp=0;
break;
}
}
}
return lists;
}
}
滑动窗口:
滑动窗口的操作
扩大窗口,j += 1
缩小窗口,i += 1
算法步骤:
- 初始化,i=1,j=1, 表示窗口大小为0
- 如果窗口中值的和小于目标值sum, 表示需要扩大窗口,j += 1
- 否则,如果狂口值和大于目标值sum,表示需要缩小窗口,i += 1
- 否则,等于目标值,存结果,缩小窗口,继续进行步骤2,3,4
import java.util.ArrayList;
public class Solution {
public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
ArrayList<ArrayList<Integer> >lists=new ArrayList<>();
int left=1,right=1;
int tmp=0;
while(left<=sum/2){
if(tmp==sum){
ArrayList<Integer> list=new ArrayList<>();
for(int i=left;i<right;i++){
list.add(i);
}
lists.add(list);
tmp-=left;
left++;
}else if(tmp<sum){
tmp+=right;
right++;
}else{
tmp-=left;
left++;
}
}
return lists;
}
}
4.2 孩子们的游戏(圆圈中最后剩下的数)
用list模拟循环链表
import java.util.*;
public class Solution {
public int LastRemaining_Solution(int n, int m) {
List<Integer> list=new LinkedList<>();
for(int i=0;i<n;i++){
list.add(i);
}
int cur=0;
while(n>1){
cur=(cur+m-1)%n;
list.remove(cur);
n--;
}
return list.get(0);
}
}
约瑟夫环:
使用递归:
import java.util.*;
public class Solution {
public int LastRemaining_Solution(int n, int m) {
if(n<1) return -1;
return f(n,m);
}
private int f(int n,int m){
if(n==1)return 0;
int x=f(n-1,m);
return (x+m)%n;
}
}
使用迭代:
f[1] = 0
f[2] = (f{1] + m) % 2
f[3] = (f[2] + m) % 3
…
f[n] = (f[n-1] + m) % n
所以代码如下:
import java.util.*;
public class Solution {
public int LastRemaining_Solution(int n, int m) {
if(n<1) return -1;
int index=0;
for(int i=2;i<=n;i++){
index=(index+m)%i;
}
return index;
}
}
4.3 字符流中第一个不重复的字符
import java.util.*;
public class Solution {
int[] arr = new int[128];
Queue<Character> q = new LinkedList<>();
//Insert one char from stringstream
public void Insert(char ch)
{
if(arr[ch]++==0){
//arr[ch]++;
q.add(ch);//新来的单身字符,入队
}
}
//return the first appearence once char in current stringstream
public char FirstAppearingOnce()
{
Character ch = null;
char c = 0;
while((ch=q.peek())!=null){
c = ch.charValue();
if(arr[c]==1){
return c;//判断是否脱单了,没脱单则输出
}else{
q.remove();//脱单了就移出队列,它不会再回来了
}
}
return '#';//队空,返回#
}
}
4.4 调整数组顺序使奇数位于偶数前面(二)
快排思想
import java.util.*;
public class Solution {
public int[] reOrderArrayTwo (int[] array) {
int left=0,right=array.length-1;
while(left<right){
while(left<right&&array[left]%2==1)
left++;
while(left<right&&array[right]%2==0)
right--;
swap(left,right,array);
}
return array;
}
private void swap(int i,int j,int[] array){
int temp=array[i];
array[i]=array[j];
array[j]=temp;
}
}
4.5 球会落何处
class Solution {
public int[] findBall(int[][] grid) {
int rLen=grid.length,cLen=grid[0].length;
int[] ans=new int[cLen];
for(int i=0;i<cLen;i++){
ans[i]=i;
}
for(int i=0;i<rLen;i++){
for(int j=0;j<cLen;j++){
if(ans[j]==-1) continue;
//记录下当前位置挡板情况
int dir=grid[i][ans[j]];
//移动
ans[j]+=dir;
//若移动后的位置超出网格,或者移动后的位置的挡板方向与移动前的位置的挡板方向不一致
if(ans[j]<0||ans[j]==cLen||dir!=grid[i][ans[j]]){
ans[j]=-1;
}
}
}
return ans;
}
}
4.6 复数乘法
思路:主要是考察String.split()的使用,正则表达式
class Solution {
public String complexNumberMultiply(String num1, String num2) {
String[] n1=num1.split("\\+|i");
String[] n2=num2.split("\\+|i");
int a1=Integer.parseInt(n1[0]);
int a2=Integer.parseInt(n1[1]);
int b1=Integer.parseInt(n2[0]);
int b2=Integer.parseInt(n2[1]);
return (a1*b1-a2*b2)+"+"+(a1*b2+a2*b1)+"i";
}
}
5 动态规划
5.1 剪绳子
动态规划:
import java.util.*;
public class Solution {
public int cutRope(int target) {
if(target==2)
return 1;
if(target==3)
return 2;
int[] muti=new int[target+1];
for(int i=1;i<=4;i++)
muti[i]=i;
for(int i=5;i<=target;i++)
for(int j=1;j<i;j++)
muti[i]=Math.max(muti[i],muti[i-j]*j);
return muti[target];
}
}
数学方法:
public class Solution {
public int cutRope(int target) {
int n=target/3;
int x=target%3;
int re=1;
if(x==1){
x=4;
n--;
}
for(int i=0;i<n;i++){
re*=3;
}
return(x==0)?re:re*x;
}
}
5.2 剪绳子2
与剪绳子相比,,数据范围大大增加,所以不能使用传统的乘法运算,要使用快速幂算法进行
import java.util.*;
public class Solution {
//快速幂函数求a^n次方的结果
long pow(long a, long n){
final int MOD = 998244353;;
long ans = 1;
while(n>0){
if(n%2 == 1)ans = (ans*a)%MOD;
a = (a*a)%MOD;
n/=2;
}
return ans;
}
public long cutRope (long number) {
if(number == 2)return 1;
if(number == 3)return 2;
if(number % 3 == 0)return pow(3,number/3);
else if(number % 3 == 1)return 4*pow(3,(number-4)/3)%998244353;
else return (2*pow(3,(number-2)/3))%998244353;
}
}
6 数组
6.1 寻找两个正序数组的中位数
思路1:先合并在排序,然后取中位数,但是这样做的话,时间复杂度为O(n+m)
思路2:要想时间复杂度为O(log(n+m)),一想到就是二分查询
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int len1=nums1.length,len2=nums2.length;
int len=len1+len2;
double re=0;
if(len%2==1){//奇数
int midIndex=len/2;
re=getKthElement(nums1,nums2,midIndex+1);
}else{//偶数
int midIndex1 = len / 2 - 1, midIndex2 = len / 2;
re= (getKthElement(nums1, nums2, midIndex1 + 1) + getKthElement(nums1, nums2, midIndex2 + 1)) / 2.0;
}
return re;
}
public int getKthElement(int[] nums1,int[] nums2,int k){
int len1=nums1.length,len2=nums2.length;
int index1=0,index2=0;
int kthElement=0;
while(true){
if(index1==len1){//nums1用完,只用再在nums2中找
return nums2[index2+k-1];
}
if(index2==len2){//nums2用完,只用再在nums1中找
return nums1[index1+k-1];
}
if(k==1){//只用再找一个数时,就找两个数组中当前index中较小的那个
return Math.min(nums1[index1],nums2[index2]);
}
//一般情况
int half=k/2;//将k 1分为2,分别在num1和num2中找
// min是为了防止数据越界,-1是因为减去才是下标
int newIndex1=Math.min(index1+half,len1)-1;
int newIndex2=Math.min(index2+half,len2)-1;
// 各数组得到新下标的对应的值newVal
int newVal1=nums1[newIndex1],newVal2=nums2[newIndex2];
//若newVal1小于newVal2说明可以淘汰nums1下标处于[index1, newIndex1]对应的数
if(newVal1<=newVal2){
k-=(newIndex1-index1+1);
index1=newIndex1+1;
}else{
k-=(newIndex2-index2+1);
index2=newIndex2+1;
}
}
}
}
6.2 盛最多水的容器
思路1:使用动态规划,两个for
思路2:使用双指针。
class Solution {
public int maxArea(int[] height) {
int left=0,right=height.length-1;
int max=0;
while(left<right){
max=Math.max(max,getArea(height,left,right));
if(height[left]>height[right]){
right--;
}else{
left++;
}
}
return max;
}
public int getArea(int[] height,int i,int j){
return (j-i)*Math.min(height[i],height[j]);
}
}
6.3 三数之和
思路:先排序然后将第一个遇到的数的相反数,当作后两个数的和,使用使用双指针遍历后面的情况
ans.add(new ArrayList<>(Arrays.asList(nums[i], nums[left], nums[right])));
这里的Arrays.asList要先套一层new ArrayList<>(),而不能直接被添加的原因是Arrays.asList()返回固定尺寸的List,不能再修改
thinking in java给的解释是:把Arrays.asList()的结果作为构造器的参数传递给任何Collection。
class Solution {
public List<List<Integer>> threeSum(int[] nums) {// 总时间复杂度:O(n^2)
List<List<Integer>> ans = new ArrayList<>();
if (nums == null || nums.length <= 2) return ans;
Arrays.sort(nums); // O(nlogn)
for (int i = 0; i < nums.length - 2; i++) { // O(n^2)
if (nums[i] > 0) break; // 第一个数大于 0,后面的数都比它大,肯定不成立了
if (i > 0 && nums[i] == nums[i - 1]) continue; // 去掉重复情况
int target = -nums[i];
int left = i + 1, right = nums.length - 1;
while (left < right) {
if (nums[left] + nums[right] == target) {
ans.add(new ArrayList<>(Arrays.asList(nums[i], nums[left], nums[right])));
// 现在要增加 left,减小 right,但是不能重复,比如: [-2, -1, -1, -1, 3, 3, 3], i = 0, left = 1, right = 6, [-2, -1, 3] 的答案加入后,需要排除重复的 -1 和 3
left++; right--; // 首先无论如何先要进行加减操作
while (left < right && nums[left] == nums[left - 1]) left++;
while (left < right && nums[right] == nums[right + 1]) right--;
} else if (nums[left] + nums[right] < target) {
left++;
} else { // nums[left] + nums[right] > target
right--;
}
}
}
return ans;
}
}
6.4 三数最接近的和
思路和6.2类似但注意:ans的初值不能设为MAX_VALUE,因为在加减的时候就容易超出范围
class Solution {
public int threeSumClosest(int[] nums, int target) {
int ans=100000;
if (nums == null || nums.length <= 2) return ans;
Arrays.sort(nums); // O(nlogn)
for (int i = 0; i < nums.length-2; i++) { // O(n^2)
if (i > 0 && nums[i] == nums[i - 1]) continue; // 去掉重复情况
int left = i + 1, right = nums.length - 1;
while (left < right) {
int sum=nums[i] + nums[left] + nums[right];
if (sum==target){
return target;
}
if(Math.abs(ans-target)>Math.abs(sum-target)){
ans=sum;
}
if (sum < target) {
left++;
} else { // nums[left] + nums[right] > target
right--;
}
}
}
return ans;
}
}
6.5 下一个排列
思路:
- 我们需要将一个左边的「较小数」与一个右边的「较大数」交换,以能够让当前排列变大,从而得到下一个排列。
- 同时我们要让这个「较小数」尽量靠右,而「较大数」尽可能小。当交换完成后,「较大数」右边的数需要按照升序重新排列。这样可以在保证新排列大于原来排列的情况下,使变大的幅度尽可能小。
- 交换以后,要将左边「较小数」以右的数列反转,令右边变成一个升序
class Solution {
public void nextPermutation(int[] nums) {
int i = nums.length - 2;
//找到从后往前数第一个前面数字比后面数字小的地方
while (i >= 0 && nums[i] >= nums[i + 1]) {
i--;
}
if (i >= 0) {
int j = nums.length - 1;
//从i的后面找到比i大,但是差距最小的数
while (j >= i && nums[i] >= nums[j]) {
j--;
}
swap(nums, i, j);
}
reverse(nums, i + 1);
}
public void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
public void reverse(int[] nums, int start) {
int left = start, right = nums.length - 1;
while (left < right) {
swap(nums, left, right);
left++;
right--;
}
}
}
6.6 在排序数组中查找元素的第一个和最后一个位置
思路:很简单的二分查找
class Solution {
public int[] searchRange(int[] nums, int target) {
int left=0,right=nums.length-1;
while(left<=right){
int mid=(left+right)>>1;
if(nums[mid]==target){
int[] re=new int[]{mid,mid};
while(re[0]>=0&&nums[re[0]]==target){
re[0]--;
}
while(re[1]<=nums.length-1&&nums[re[1]]==target){
re[1]++;
}
re[0]++;re[1]--;
return re;
}else if(nums[mid]<target){
left=mid+1;
}else{
right=mid-1;
}
}
return new int[]{-1,-1};
}
}
6.7 有效的数独
思路:分别用9个数组记录行,列,九宫格的1~9个数,没出现时都为false,出现了,将下标为对应数的记录为true,若有重复返回错误,用idx根据i,j计算出当前数在哪个九宫格中
class Solution {
public boolean isValidSudoku(char[][] board) {
boolean[][] rows=new boolean[9][9],cols=new boolean[9][9],boxes=new boolean[9][9];
for(int i=0;i<9;i++){
for(int j=0;j<9;j++){
if(board[i][j]=='.') continue;
int num=board[i][j]-'1';
int idx=3*(i/3)+j/3;//找到对应的9宫格
if(!rows[i][num]&&!cols[num][j]&&!boxes[idx][num]){
rows[i][num]=true;
cols[num][j]=true;
boxes[idx][num]=true;
}else{
return false;
}
}
}
return true;
}
}
6.8 解数独
思路:使用回溯法的子集树,找到一个解
注意valid变量的使用:可以使你找到解以后不用再回溯,以免变成错误答案
class Solution {
public void solveSudoku(char[][] board) {
boolean[][] rows=new boolean[9][9],cols=new boolean[9][9],boxes=new boolean[9][9];
//初始化现有数字
for(int i=0;i<9;i++){
for(int j=0;j<9;j++){
int idx=3*(i/3)+j/3;
if(board[i][j]!='.'){
int num=board[i][j]-'1';
rows[i][num]=true;
cols[j][num]=true;
boxes[idx][num]=true;
}
}
}
//填剩余数字
dps(board,rows,cols,boxes,0,0);
}
public void dps(char[][] board,boolean[][] rows,boolean[][] cols,boolean[][] boxes,int i,int j){
if(i>8){
return;
}else{
int idx=3*(i/3)+j/3;
if(board[i][j]=='.'){
for(int k=0;k<9;k++){
board[i][j]=(char)(k+'1');
if(legal(rows,cols,boxes,i,j,idx,k)){
j++;
if(j==9){
i++;
j=0;
}
dps(board,rows,cols,boxes,i,j);
}
}
}else{
j++;
if(j==9){
i++;
j=0;
}
}
}
}
public boolean legal(boolean[][] rows,boolean[][] cols,boolean[][] boxes,int i,int j,int idx,int k){
if(!rows[i][k]&&!cols[j][k]&&!boxes[idx][k]){
rows[i][k]=true;
cols[j][k]=true;
boxes[idx][k]=true;
return true;
}
return false;
}
}
6.9 组合总数
思路:使用回溯法,无剪枝
class Solution {
List<List<Integer>> re=new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
//Arrays.sort(candidates);
List<Integer> list=new ArrayList<>();
dfs(candidates,target,0,list);
return re;
}
public void dfs(int[] candidates,int sum,int i,List<Integer> list){
if(i==candidates.length){
return;
}else{
if(sum==0){
re.add(new ArrayList<Integer>(list));
return;
}
//不选取当前元素
dfs(candidates,sum,i+1,list);
//选取当前元素
if(sum>=candidates[i]){
list.add(candidates[i]);
sum-=candidates[i];
dfs(candidates,sum,i,list);
//sum+=candidates[i];
list.remove(list.size()-1);
}
}
}
}
先排序,有剪枝
class Solution {
List<List<Integer>> re=new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
Arrays.sort(candidates);
List<Integer> list=new ArrayList<>();
dfs(candidates,target,0,list);
return re;
}
public void dfs(int[] candidates,int sum,int begin,List<Integer> list){
if(sum==0){
re.add(new ArrayList<>(list));
return;
}
for(int i=begin;i<candidates.length;i++){
if(sum>=candidates[i]){
list.add(candidates[i]);
dfs(candidates,sum-candidates[i],i,list);
list.remove(list.size()-1);
}else{
break;
}
}
}
}
6.10 组合总数(二)
思路:与组合总数相似。但是使用了if(i > idx && candidates[i] == candidates[i-1]) continue;
来去重
import java.util.*;
class Solution {
private List<List<Integer>> res;
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
res=new ArrayList<>();
List<Integer> list=new ArrayList<>();
Arrays.sort(candidates);
dfs(candidates,list,target,0);
return res;
}
public void dfs(int[] candidates,List<Integer> list,int target,int idx){
if(target==0){
res.add(new ArrayList<>(list));
return;
}else{
for(int i=idx;i<candidates.length;i++){
//出现这个情况的只有位置为i-1这个数没有加入list,但是位置为i加入了list,正好i和i-1的数相等
if(i > idx && candidates[i] == candidates[i-1]) continue;
if(target>=candidates[i]){
list.add(candidates[i]);
dfs(candidates,list,target-candidates[i],i+1);
list.remove(list.size()-1);
}else{
break;
}
}
}
}
}
6.11 缺失的第一个正数
思路1: 用一个长度为n+2的boolean数组表示1-n是否出现过,若出现过则设为true。最后从左到右遍历boolean数组,遇到的第一个false的下标就是我们要找的数(但不是常数级别的空间复杂度)
class Solution {
public int firstMissingPositive(int[] nums) {
boolean[] flags=new boolean[nums.length+2];
for(int i=0;i<nums.length;i++){
if(nums[i]>0&&nums[i]<flags.length)
flags[nums[i]]=true;
}
for(int i=1;i<flags.length;i++){
if(flags[i]==false){
return i;
}
}
return -1;
}
}
思路2:使用交换的思想,遍历到一个在[1-n]的数,就将它交换到正确的位置,最后遍历数组,找到第一个下标和值不同的位置,就是答案
class Solution {
public int firstMissingPositive(int[] nums) {
int n = nums.length;
for (int i = 0; i < n; ++i) {
while (nums[i] > 0 && nums[i] <= n && nums[nums[i] - 1] != nums[i]) {
int temp = nums[nums[i] - 1];
nums[nums[i] - 1] = nums[i];
nums[i] = temp;
}
}
for (int i = 0; i < n; ++i) {
if (nums[i] != i + 1) {
return i + 1;
}
}
return n + 1;
}
}
6.12 接雨水
求最优,想动态规划
import java.util.*;
class Solution {
public int trap(int[] height) {
//求最优,想动态规划
//初始化两个数组leftMaxH和rightMaxH分别表示该位置向左最大高度和向右最大高度
int len=height.length;
if(len<=2) return 0;
int[] leftMaxH=new int[len];
leftMaxH[0]=height[0];
for(int i=1;i<len;i++){
leftMaxH[i]=Math.max(leftMaxH[i-1],height[i]);
}
int[] rightMaxH=new int[len];
rightMaxH[len-1]=height[len-1];
for(int i=len-2;i>=0;i--){
rightMaxH[i]=Math.max(rightMaxH[i+1],height[i]);
}
//当前位置储水量=(向左最大高度和向右最大高度的最小值)-当前柱子的高度
int sum=0;
for(int i=0;i<len;i++){
sum+=Math.min(leftMaxH[i],rightMaxH[i])-height[i];
}
return sum;
}
}
6.13 跳跃游戏
import java.util.*;
class Solution {
public int jump(int[] nums) {
//动态规划
//steps[i]表示到达第i位置的最少步数
int len=nums.length;
int[] steps=new int[len];
int start=0;//start表示从这里开始,才有可能达到当前位置
for(int i=1;i<len;i++){
for(int j=start;j<i;j++){
if(j+nums[j]>=i){
start=j;//j之前的位置都达不到i,那更达不到比i大的地方
steps[i]=steps[j]+1;
break;
}
}
}
return steps[len-1];
}
}
6.14 全排列
思路:回溯
class Solution {
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> res = new ArrayList<List<Integer>>();
List<Integer> output = new ArrayList<Integer>();
for (int num : nums) {
output.add(num);
}
int n = nums.length;
backtrack(n, output, res, 0);
return res;
}
public void backtrack(int n, List<Integer> output, List<List<Integer>> res, int first) {
// 所有数都填完了
if (first == n) {
res.add(new ArrayList<Integer>(output));
}
for (int i = first; i < n; i++) {
// 动态维护数组
Collections.swap(output, first, i);
// 继续递归填下一个数
backtrack(n, output, res, first + 1);
// 撤销操作
Collections.swap(output, first, i);
}
}
}
class Solution {
List<List<Integer>> res;
public List<List<Integer>> permute(int[] nums) {
res = new ArrayList<List<Integer>>();
List<Integer> path = new ArrayList<Integer>();
boolean[] used=new boolean[nums.length];
int n = nums.length;
backtrack(n,used, nums,path, 0);
return res;
}
public void backtrack(int n,boolean[] used, int[] nums,List<Integer> path, int depth) {
if (depth == n) {
res.add(new ArrayList<Integer>(path));
}
for (int i = 0; i < n; i++) {
if(!used[i]){
used[i]=true;
path.add(nums[i]);
backtrack(n,used,nums,path,depth+1);
path.remove(path.size()-1);
used[i]=false;
}
}
}
}
6.15 全排列2
思路:首先映入脑中的是用Set记录已出现的序列,但是发现时间,空间复杂度太高了
于是使用if(used[i]||(i>0&&nums[i]==nums[i-1]&&!used[i-1])) continue;
跳过在同一位置填入相同数字的情况
class Solution {
List<List<Integer>> res;
public List<List<Integer>> permuteUnique(int[] nums) {
res = new ArrayList<List<Integer>>();
List<Integer> path = new ArrayList<Integer>();
boolean[] used=new boolean[nums.length];
Arrays.sort(nums);
int n = nums.length;
backtrack(n,used, nums,path, 0);
return res;
}
public void backtrack(int n,boolean[] used, int[] nums,List<Integer> path, int depth) {
if (depth == n) {
res.add(new ArrayList<Integer>(path));
}
for (int i = 0; i < n; i++) {
if(used[i]||(i>0&&nums[i]==nums[i-1]&&!used[i-1]))
continue;
used[i]=true;
path.add(nums[i]);
backtrack(n,used,nums,path,depth+1);
path.remove(path.size()-1);
used[i]=false;
}
}
}
6.16 旋转图像
思路:从外到内逐层旋转
class Solution {
public void rotate(int[][] matrix) {
rot(matrix,0,matrix.length-1);
}
//递归
public void rot(int[][] matrix,int left,int right){
if(left>=right){
return;
}
//旋转
int len=right-left;
for(int i=0;i<right-left;i++){
int temp=matrix[left][left+i];
matrix[left][left+i]=matrix[right-i][left];
matrix[right-i][left]=matrix[right][right-i];
matrix[right][right-i]=matrix[left+i][right];
matrix[left+i][right]=temp;
}
rot(matrix,left+1,right-1);
}
}
思路2:交换各个子矩阵的值
class Solution {
public void rotate(int[][] matrix) {
int n = matrix.length;
for (int i = 0; i < n / 2; ++i) {
for (int j = 0; j < (n + 1) / 2; ++j) {
int temp = matrix[i][j];
matrix[i][j] = matrix[n - j - 1][i];
matrix[n - j - 1][i] = matrix[n - i - 1][n - j - 1];
matrix[n - i - 1][n - j - 1] = matrix[j][n - i - 1];
matrix[j][n - i - 1] = temp;
}
}
}
}
6.17 仅仅反转字母
思路:双指针
class Solution {
public String reverseOnlyLetters(String s) {
char[] charArray=s.toCharArray();
int left=0,right=s.length()-1;
while(left<right){
while(left<right&&(!Character.isLetter(charArray[left]))){
left++;
}
while(left<right&&(!Character.isLetter(charArray[right]))){
right--;
}
swap(charArray,left,right);
left++;right--;
}
return new String(charArray);
}
public void swap(char[] charArray,int left,int right){
char temp=charArray[left];
charArray[left]=charArray[right];
charArray[right]=temp;
}
}
6.18 n皇后问题
import java.util.*;
class Solution {
int[] re;
List<List<String>> lists=new ArrayList<>();
public List<List<String>> solveNQueens(int n) {
re=new int[n];
dfs(0,n);
return lists;
}
//回溯法遍历
public void dfs(int level,int n){
if(level==n){
lists.add(generateString(re));
}else{
for(int i=0;i<n;i++){
re[level]=i;
if(bound(level)){
dfs(level+1,n);
}
}
}
}
//将int x[i]转换成题目要求的字符串
public List<String> generateString(int[] queens){
List<String> result=new ArrayList<>();
for(int i:queens){
char[] chars=new char[queens.length];
Arrays.fill(chars,'.');
chars[i]='Q';
result.add(String.valueOf(chars));
}
return result;
}
//限界函数,同一列重合,斜着重合则减枝
public boolean bound(int level){
for(int i=0;i<level;i++){
if(Math.abs(re[i]-re[level])==Math.abs(i-level)||(re[i]==re[level])){
return false;
}
}
return true;
}
}
6.19 最大子数组和
思路:动态规划
class Solution {
public int maxSubArray(int[] nums) {
int len=nums.length;
int maxi=nums[0];//以i结尾的最大和
int re=nums[0];
for(int i=1;i<len;i++){
maxi=maxi>0?maxi+nums[i]:nums[i];
if(maxi>re){
re=maxi;
}
}
return re;
}
}
6.20 螺旋矩阵
思路:从外到内层次添加
递归:
class Solution {
public static List<Integer> list;
public List<Integer> spiralOrder(int[][] matrix) {
list=new ArrayList<>();
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return list;
}
int row=matrix.length;
int col=matrix[0].length;
dfs(matrix,0,row-1,col-1);
return list;
}
public void dfs(int[][] matrix,int level,int row,int col){
int left=level;int right=col-level;
int top=level;int bottom=row-level;
if(left<=right&&top<=bottom){
for(int i=left;i<=right;i++){
list.add(matrix[top][i]);
}
for(int i=top+1;i<=bottom;i++){
list.add(matrix[i][right]);
}
if(left<right&&top<bottom) {
for (int i = right - 1; i > left; i--) {
list.add(matrix[bottom][i]);
}
for (int i = bottom; i >top; i--) {
list.add(matrix[i][left]);
}
}
dfs(matrix,level+1,row,col);
}
}
}
迭代:
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
List<Integer> order = new ArrayList<Integer>();
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return order;
}
int rows = matrix.length, columns = matrix[0].length;
int left = 0, right = columns - 1, top = 0, bottom = rows - 1;
while (left <= right && top <= bottom) {
for (int column = left; column <= right; column++) {
order.add(matrix[top][column]);
}
for (int row = top + 1; row <= bottom; row++) {
order.add(matrix[row][right]);
}
if (left < right && top < bottom) {
for (int column = right - 1; column > left; column--) {
order.add(matrix[bottom][column]);
}
for (int row = bottom; row > top; row--) {
order.add(matrix[row][left]);
}
}
left++;
right--;
top++;
bottom--;
}
return order;
}
}
6.21 螺旋矩阵 II
class Solution {
public int[][] generateMatrix(int n) {
int [][] re=new int[n][n];
int s=0,e=n-1;
int num=1;
while(s<=e){
for(int i=s;i<=e;i++){
re[s][i]=num++;
}
for(int i=s+1;i<=e;i++){
re[i][e]=num++;
}
if(s<e){
for(int i=e-1;i>s;i--){
re[e][i]=num++;
}
for(int i=e;i>s;i--){
re[i][s]=num++;
}
}
s++;e--;
}
return re;
}
}
6.22 跳跃游戏
从前往后
class Solution {
public boolean canJump(int[] nums) {
int arrive=0;
int i=0;
while(i<=arrive){
if(arrive>=nums.length-1) return true;
if(i+nums[i]>arrive) {
arrive=i+nums[i];
}
i++;
}
return false;
}
}
从后往前
class Solution {
public boolean canJump(int[] nums) {
int step = 1;
for(int i=nums.length-2;i>=0;i--){
//从后往前,判断上一个能否到达现在位置
if(nums[i] >= step){//若可以,则将上一个作为当前位置
step = 1;
}else{//若不行,则让到达当前位置的距离+1
step ++;
}
}
return step == 1;
}
}
6.23 最优除法
注意sb.insert(po,str)的使用
class Solution {
public String optimalDivision(int[] nums) {
//分子最大为nums[0]。分母最小为nums[1]/nums[2]/.../nums[nums.length-1]
StringBuilder sb=new StringBuilder();
for(int i=0;i<nums.length;i++){
sb.append(nums[i]);
if(i<nums.length-1){
sb.append("/");
}
}
if(nums.length>2){
sb.insert(sb.indexOf("/")+1,"(");
sb.append(")");
}
return sb.toString();
}
}
6.24 合并区间
注意comparator的使用,下来学习一下
import java.util.*;
class Solution {
public int[][] merge(int[][] intervals) {
// Arrays.sort(intervals,new Comparator<int[]>(){
// public int compare(int[] intervals1,int[] intervals2){
// return intervals1[0]-intervals2[0];
// }
// });
Arrays.sort(intervals, (int[] o1, int[] o2) -> o1[0] - o2[0]);
List<int[]> lists=new ArrayList<>();
int[] pre=intervals[0];
for(int i=1;i<intervals.length;i++){
int[] cur=intervals[i];
if(cur[0]<=pre[1]){
if(pre[1]<cur[1])
pre[1]=cur[1];
}else{
lists.add(pre);
pre=cur;
}
}
lists.add(pre);
return lists.toArray(new int[lists.size()][]);
}
}
6.25 插入区间
class Solution {
public int[][] insert(int[][] intervals, int[] newInterval) {
List<int[]> reList=new ArrayList<>();
int left=newInterval[0],right=newInterval[1];
int i=0;
for(;i<intervals.length;i++){
// 确定前沿
if(intervals[i][1]<left){
reList.add(intervals[i]);
continue;
}else{
if(intervals[i][0]<left){
left=intervals[i][0];
}
}
//确定后沿
if(intervals[i][0]<=right){
if(intervals[i][1]>right){
right=intervals[i][1];
}
}else{
break;
}
}
//之后的直接添加
reList.add(new int[]{left,right});
for(;i<intervals.length;i++){
reList.add(intervals[i]);
}
return reList.toArray(new int[reList.size()][]);
}
}
思路:通过滚动数组优化后的动态规划
class Solution {
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
int row = obstacleGrid.length, col = obstacleGrid[0].length;
//dp[j]+=dp[j-1]->dp[j]=dp[j]+dp[j-1]表示dp[i][j]=dp[i-1][j]+dp[i][j-1]
int[] dp = new int[col];
dp[0] = obstacleGrid[0][0] == 0 ? 1 : 0;
for (int i = 0; i < row; ++i) {
for (int j = 0; j < col; ++j) {
if (obstacleGrid[i][j] == 1) {
dp[j] = 0;
continue;
}
//若没有超出界限,且当前位置的上一行是可通行的,则加上到达当前位置上一行的路径数,不然就保持原样
if (j - 1 >= 0 && obstacleGrid[i][j - 1] == 0) {
dp[j] += dp[j - 1];
}
}
}
return dp[col - 1];
}
}
6.26 不同路径2
class Solution {
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
//obstacleGrid[i][j]!=0时,paths[i][j]=paths[i-1][j]+paths[i][j-1]
if(obstacleGrid[0][0]==1) return 0;
int row=obstacleGrid.length,col=obstacleGrid[0].length;
int[][] paths=new int[row][col];
int x=0,y=0;
while(y<col&&obstacleGrid[0][y]!=1){
paths[0][y]=1;
y++;
}
while(x<row&&obstacleGrid[x][0]!=1){
paths[x][0]=1;
x++;
}
for(int i=1;i<row;i++){
for(int j=1;j<col;j++){
if(obstacleGrid[i][j]==0){
paths[i][j]=paths[i-1][j]+paths[i][j-1];
}
}
}
return paths[row-1][col-1];
}
}
6.27 最小路径和
思路:动态规划
class Solution {
public int minPathSum(int[][] grid) {
int row=grid.length,col=grid[0].length;
int[][] dp=new int[row][col];
dp[0][0]=grid[0][0];
for(int i=1;i<row;i++){
dp[i][0]=grid[i][0]+dp[i-1][0];
}
for(int j=1;j<col;j++){
dp[0][j]=grid[0][j]+dp[0][j-1];
}
for(int i=1;i<row;i++){
for(int j=1;j<col;j++){
dp[i][j]=Math.min(dp[i][j-1],dp[i-1][j])+grid[i][j];
}
}
return dp[row-1][col-1];
}
}
时间复杂度:O(mn),其中 m 和 n 分别是网格的行数和列数。需要对整个网格遍历一次,计算 dp 的每个元素的值。
空间复杂度:O(mn),其中 m 和 n分别是网格的行数和列数。创建一个二维数组dp,和网格大小相同。
空间复杂度可以优化,例如每次只存储上一行的 dp 值,则可以将空间复杂度优化到 O(n)。
优化:
class Solution {
public int minPathSum(int[][] grid) {
int row=grid.length,col=grid[0].length;
int[] dp=new int[row];
dp[0]=grid[0][0];
for(int i=1;i<row;i++){
dp[i]=grid[i][0]+dp[i-1];
}
for(int j=1;j<col;j++){
dp[0]=dp[0]+grid[0][j];
for(int i=1;i<row;i++){
dp[i]=Math.min(dp[i],dp[i-1])+grid[i][j];
}
}
return dp[row-1];
}
}
6.28 矩阵理论
思路1:
class Solution {
public void setZeroes(int[][] matrix) {
boolean[] rows=new boolean[matrix.length];
boolean[] cols=new boolean[matrix[0].length];
for(int i=0;i<matrix.length;i++){
for(int j=0;j<matrix[0].length;j++){
if(matrix[i][j]==0){
rows[i]=true;
cols[j]=true;
}
}
}
for(int i=0;i<matrix.length;i++){
if(rows[i]){
for(int j=0;j<matrix[0].length;j++){
matrix[i][j]=0;
}
}
}
for(int j=0;j<matrix[0].length;j++){
if(cols[j]){
for(int i=0;i<matrix.length;i++){
matrix[i][j]=0;
}
}
}
}
}
思路2:
我们可以用矩阵的第一行和第一列代替方法一中的两个标记数组,以达到 O(1)O(1)O(1) 的额外空间。但这样会导致原数组的第一行和第一列被修改,无法记录它们是否原本包含 000。因此我们需要额外使用两个标记变量分别记录第一行和第一列是否原本包含 000。
在实际代码中,我们首先预处理出两个标记变量,接着使用其他行与列去处理第一行与第一列,然后反过来使用第一行与第一列去更新其他行与列,最后使用两个标记变量更新第一行与第一列即可。
class Solution {
public void setZeroes(int[][] matrix) {
int m = matrix.length, n = matrix[0].length;
boolean flagCol0 = false, flagRow0 = false;
for (int i = 0; i < m; i++) {
if (matrix[i][0] == 0) {
flagCol0 = true;
}
}
for (int j = 0; j < n; j++) {
if (matrix[0][j] == 0) {
flagRow0 = true;
}
}
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
if (matrix[i][j] == 0) {
matrix[i][0] = matrix[0][j] = 0;
}
}
}
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
if (matrix[i][0] == 0 || matrix[0][j] == 0) {
matrix[i][j] = 0;
}
}
}
if (flagCol0) {
for (int i = 0; i < m; i++) {
matrix[i][0] = 0;
}
}
if (flagRow0) {
for (int j = 0; j < n; j++) {
matrix[0][j] = 0;
}
}
}
}
7 每天一题
7.1 最多可达成的换楼请求数目
思路:回溯法
class Solution {
int[]dem;
int max=0;
int re=0;
public int maximumRequests(int n, int[][] requests) {
dem=new int[n];
dfs(0,requests);
return max;
}
public void dfs(int level,int[][] requests){
if(level==requests.length){
if(check(dem)){
max=Math.max(max,re);
}
return;
}
//不选当前请求
dfs(level+1,requests);
//选取当前请求
re++;
dem[requests[level][0]]--;
dem[requests[level][1]]++;
dfs(level+1,requests);
dem[requests[level][1]]--;
dem[requests[level][0]]++;
re--;
}
public boolean check(int[] dem){
for(int i=0;i<dem.length;i++){
if(dem[i]!=0)
return false;
}
return true;
}
}
思路二:二进制枚举
class Solution {
public int maximumRequests(int n, int[][] requests) {
int[] delta = new int[n];
int ans = 0, m = requests.length;
for (int mask = 0; mask < (1 << m); ++mask) {
int cnt = Integer.bitCount(mask);
if (cnt <= ans) {
continue;
}
Arrays.fill(delta, 0);
for (int i = 0; i < m; ++i) {
if ((mask & (1 << i)) != 0) {
++delta[requests[i][0]];
--delta[requests[i][1]];
}
}
boolean flag = true;
for (int x : delta) {
if (x != 0) {
flag = false;
break;
}
}
if (flag) {
ans = cnt;
}
}
return ans;
}
}
7.2 Z自行变换
思路1:矩阵模拟
class Solution {
public String convert(String s, int numRows) {
int n=s.length();
int r=numRows;
if(r==1||r>=s.length()) return s;
int t = r * 2 - 2;
int c = (n + t - 1) / t * (r - 1);
char[][] re=new char[r][c];
char[] chars=s.toCharArray();
for(int i=0,x=0,y=0;i<n;i++){
re[x][y] = chars[i];
if(i%t<r-1){
x++;
}else{
x--;
y++;
}
}
StringBuilder sb=new StringBuilder();
for(int k=0;k<r;k++){
for(int j=0;j<c;j++){
if(re[k][j]!='\0')
sb.append(re[k][j]);
}
}
return sb.toString();
}
}
思路2:压缩矩阵
方法一中的矩阵有大量的空间没有被使用,能否优化呢?
注意到每次往矩阵的某一行添加字符时,都会添加到该行上一个字符的右侧,且最后组成答案时只会用到每行的非空字符。因此我们可以将矩阵的每行初始化为一个空列表,每次向某一行添加字符时,添加到该行的列表末尾即可。
class Solution {
public String convert(String s, int numRows) {
int n=s.length();
int r=numRows;
if(r==1||r>=s.length()) return s;
//为每一行初始化一个列表
StringBuilder[] sbs=new StringBuilder[r];
for(int i=0;i<r;i++){
sbs[i]=new StringBuilder();
}
char[] chars=s.toCharArray();
for(int i=0,x=0,t=r*2-2;i<n;i++){
sbs[x].append(chars[i]);
if(i%t<r-1){
x++;
}else{
x--;
}
}
StringBuilder re=new StringBuilder();
for(StringBuilder sb:sbs){
re.append(sb);
}
return re.toString();
}
}
思路三:直接构造:用mod的思想
class Solution {
public String convert(String s, int numRows) {
int n=s.length(),r=numRows;
if(r==1||r>=s.length()) return s;
//为每一行初始化一个列表
StringBuilder re=new StringBuilder();
char[] chars=s.toCharArray();
int t=r*2-2;//每轮循环需要r*2-2个字符
for(int i=0;i<r;i++){//枚举每一行
for(int j=0;j+i<n;j+=t){//枚举每个周期的起始下标
re.append(chars[i+j]);//添加当前周期该行的第一个字符
if(0<i&&i<r-1&&j+t-i<n){//除了第一行和最后一行,每行都会添加第二个字符
re.append(chars[j+t-i]);
}
}
}
return re.toString();
}
}
7.3 寻找最近的回文数
思路:上下界分析 + 边界处理(参考宫水三叶)
对于任意字符串数值 s而言(令其长度为 n),先考虑如何找到「第一个比其大」和「第一个比其小」的回文串数值(上下界)。
由于是找「最近」的数值,因此一个自然的想法是优先修改低位数字,但由于回文串本身的对称性质,每个「低位」的修改带来的副作用是需要同时修改对应的「高位」,因此一个真正可以修改的位置是(相对)中间的位置。
举个 🌰,对于长度为奇数回文串数值 abcde,其中 de 的修改会导致 ab 同步修改,因此最近一个可以修改的低位是 c;对于长度为偶数的回文串数值 abcd 而言,其中 d的修改会导致 a 同步修改,因此最近一个可以修改的位置是 bc。
当对目标位置进行修改后,其余位置的数值可由回文串性质唯一确定,因此只需要找到可修改位置相邻数值即可。
例如对于 abcde 来说,最近的回文数值的前三位可能是 abc、abc+1 和 abc−1三者之一,其他位置的数值随着前三位的确定而唯一确定。
但是有边界限制
上述分析对于一般情况成立,而边界情况是指abc + 1和 abc−1导致整体长度发生变化,即abc=999 和abc=100 的情况,此时直接套用上述方法得到的值分别为 1000001和 999,但真实最近比其大的值为 100001 和 9999,因此对于长度为 n 的数值,值考虑枚举前一半数的三种情况(前一半数不变,前一半数加一或减一)可能会过错最优解,我们需要将与长度相关的边界值也纳入考虑,即将「长度为 n−1的回文串最大值(99…99)」和「长度为 n+1的回文串最小值(10…01)」也纳入考虑,也就是对应的 10n−1−1和 10n+ 1也纳入考虑。然后从上述 5个值中选绝对差值最小的作为答案。
class Solution {
public String nearestPalindromic(String s) {
int n = s.length();
long cur = Long.parseLong(s);
Set<Long> set = new HashSet<>();
//考虑边界情况,对于100xx来说,有9999,对于999xx
set.add((long) Math.pow(10, n - 1) - 1);
set.add((long) Math.pow(10, n) + 1);
//获得前半部分数字
long t = Long.parseLong(s.substring(0, (n + 1) / 2));
//对于abcde,考虑abc-1,abc,abc+1三种情况
for (long i = t - 1; i <= t + 1; i++) {
long temp = -1;
if (n % 2 == 0) temp = getNum(i, true);
else temp = getNum(i, false);
if (temp != cur) set.add(temp);
}
//比较5个数与原数的差绝对值,取差绝对值且最小的数
long ans = -1;
for (long i : set) {
if (ans == -1)
ans = i;
else if (Math.abs(i - cur) < Math.abs(ans - cur))
ans = i;
else if (Math.abs(i - cur) == Math.abs(ans - cur) && i < ans)
ans = i;
}
return String.valueOf(ans);
}
//通过前半部分数字构造回文数字
long getNum(long k, boolean isEven) {
StringBuilder sb = new StringBuilder();
sb.append(k);
int idx = isEven ? sb.length() - 1 : sb.length() - 2;
while (idx >= 0) sb.append(sb.charAt(idx--));
return Long.parseLong(sb.toString());
}
}
7.4 各位相加
使用循环
class Solution {
public int addDigits(int num) {
while (num >= 10) {
int sum = 0;
while (num > 0) {
sum += num % 10;
num /= 10;
}
num = sum;
}
return num;
}
}
不使用循环,时间复杂度O(1)
class Solution {
public int addDigits(int num) {
return (num - 1) % 9 + 1;
}
}
7.5 子数组的范围和
思路1:枚举
class Solution {
public long subArrayRanges(int[] nums) {
int n = nums.length;
long ans = 0;
for (int i = 0; i < n; i++) {
int min = nums[i], max = nums[i];
for (int j = i + 1; j < n; j++) {
min = Math.min(min, nums[j]);
max = Math.max(max, nums[j]);
ans += max - min;
}
}
return ans;
}
}
思路2:单调栈
详情参考
import java.util.*;
class Solution {
int len;
public long subArrayRanges(int[] nums) {
// min[i] 为 nums[i] 作为区间最小值的次数;max[i] 为 nums[i] 作为区间最大值的次数
len=nums.length;
long[] min=getCnt(nums,true),max=getCnt(nums,false);
long ans=0;
//计算nums[i]对ans的贡献=nums[i]当做最大值的次数(加上的次数)-nums[i]当做最小值的次数(被减去的次数)
for(int i=0;i<len;i++){
ans+=(max[i]-min[i])*nums[i];
}
return ans;
}
public long[] getCnt(int[] nums,boolean isMin){
//单调栈:记录前一个最大或最小的位置
Deque<Integer> stack=new ArrayDeque<>();
//根据isMin的值,left[i]表示位置i的左边第一个比他(小)大的数的位置,
//right[i]表示位置i的右边第一个比他(小)大的数的位置
int[] left=new int[len],right=new int[len];
for(int i=0;i<len;i++){
while(!stack.isEmpty()&&(isMin?(nums[stack.peekLast()]>=nums[i]):(nums[stack.peekLast()]<=nums[i])))
stack.pollLast();
//如果前面比没有位置i的数更小的数了,那么left[i]=-1
left[i]=stack.isEmpty()?-1:stack.peekLast();
stack.addLast(i);
}
stack.clear();
for(int i=len-1;i>=0;i--){
while(!stack.isEmpty()&&(isMin?(nums[stack.peekLast()]>nums[i]):(nums[stack.peekLast()]<nums[i])))
stack.pollLast();
//如果后面比没有位置i的数更小的数了,那么right[i]=n
right[i]=stack.isEmpty()?len:stack.peekLast();
stack.addLast(i);
}
long[] ans=new long[len];
for(int i=0;i<len;i++){
//排列组合算位置i当最小或最大值的次数
ans[i]=(i-left[i])*(right[i]-i);
}
return ans;
}
}