04 二维数组中的查找
题目描述:
在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
题目地址:https://leetcode-cn.com/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof/
我的想法:
从数组右上角往左下角方向查找,根据数组的性质,如果往左的过程中遇到比target要小的数,就从此列从上往下找。
class Solution {
public boolean findNumberIn2DArray(int[][] matrix, int target) {
int rows = matrix.length;
if(rows==0) return false;
int cols = matrix[0].length;
if(cols==0) return false;
int i=0,j=cols-1;
while(i<rows && j>=0){
if(matrix[i][j]==target){
return true;
}else if(matrix[i][j]>target){
j--;
}else{
i++;
}
}
return false;
}
}
07 重建二叉树
题目描述:
输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
题目地址:https://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof/
我的想法:
前序遍历的顺序是:根-左-右
中序遍历的顺序是:左-根-右
每次递归,就按照前序遍历的最左边的值(作为根)去中序遍历中查找索引,然后就能把中序遍历划分成:左-根-右
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
private int num = 0;
public TreeNode findNode(int[] preorder,int[] inorder,int left,int right){
if(left>right) return null;
//查找当前结点在中序遍历的位置
//当前结点的值为preorder[left]
int idx;
//查找根节点在中序遍历中的位置
for(idx = left;idx<=right;idx++)
if(inorder[idx]==preorder[num]) break;
//如果找不到说明当前结点时null
if(idx>right) return null;
num++;
//如果找到了就继续找左右结点
TreeNode root = new TreeNode(inorder[idx]);
root.left = findNode(preorder,inorder,left,idx-1);
root.right = findNode(preorder,inorder,idx+1,right);
return root;
}
public TreeNode buildTree(int[] preorder, int[] inorder) {
int len = preorder.length;
if(len==0) return null;
TreeNode root = findNode(preorder,inorder,0,len-1);
return root;
}
}
12 矩阵中的路径
题目描述:
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径。
[[“a”,“b”,“c”,“e”],
[“s”,“f”,“c”,“s”],
[“a”,“d”,“e”,“e”]]
但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。
题目地址:https://leetcode-cn.com/problems/ju-zhen-zhong-de-lu-jing-lcof/
我的想法:
DFS和BFS方法都可以使用,DFS每次就朝上下左右一个方向搜索,宽度搜索每次向四周扩散,搜到返回true。下面代码是DFS的做法。
class Solution {
private int[] dx = {
-1,1,0,0};//上下左右对应的行变化
private int[] dy = {
0,0,-1,1};//列变化
public boolean search(int x,int y,int idx,String word,char[][] board){
if(idx==word.length()) return true;//搜索结束返回true
//越界/已经搜过/字符不对返回false结束搜索
if(x<0 || y<0 || x>=board.length || y>=board[0].length || board[x][y]=='/' || board[x][y]!=word.charAt(idx)) return false;
char tmp = board[x][y];
board[x][y] = '/';//当前到达的点先标记起来
boolean fnd = false;
for(int i=0;i<4;i++){
//每一个单元格往四个方向搜索
int tx = x + dx[i];
int ty = y + dy[i];
fnd = fnd || search(tx,ty,idx+1,word,board);
}
board[x][y] = tmp;//记得恢复原来的字符
return fnd;
}
public boolean exist(char[][] board, String word) {
int rows = board.length;
if(rows==0) return false;
int cols = board[0].length;
if(cols==0) return false;
//因为不知道起点是哪里所以需要遍历二维数组找起点
for(int i=0;i<rows;i++){
for(int j=0;j<cols;j++){
boolean flg = false;
if(board[i][j]==word.charAt(0))
flg = search(i,j,0,word,board);
if(!flg) continue;
else return true;
}
}
return false;
}
}
13 机器人的运动范围
题目描述:
地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?
题目地址:https://leetcode-cn.com/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/
我的想法:
类似上题。下面代码是DFS+BFS的做法。
//DFS:
class Solution {
private int cnt = 0;
private int[] dx = {
-1,1,0,0};
private int[] dy = {
0,0,-1,1};
private boolean[][] map;
public int getSum(int num){
int sum = 0;
while(num!=0){
sum += num%10;
num /= 10;
}
return sum;
}
public void move(int x,int y,int m,int n,int k){
if(x<0 || y<0 || x>=m || y>=n || map[x][y]) return ;//越界
int sum = getSum(x)+getSum(y);
if(sum>k) return ;//不符要求
cnt++;
map[x][y] = true;
for(int i=0;i<4;i++){
move(x+dx[i],y+dy[i],m,n,k);
}
}
public int movingCount(int m, int n, int k) {
map = new boolean[m][n];
move(0,0,m,n,k);
return cnt;
}
}
//BFS:
class Solution {
class Node{
int x,y;
public Node(int xx,int yy){
this.x = xx;this.y=yy;}
}
private int cnt = 0;
private Queue<Node> q;
private boolean[][] vis;
private int[] dx = {
-1,1,0,0};
private int[] dy = {
0,0,-1,1};
public int getSum(int num){
int sum = 0;
while(num!=0){
sum += num%10;
num /= 10;
}
return sum;
}
public int movingCount(int m, int n, int k) {
q = new LinkedList<>();
vis = new boolean[m][n];
Node node = new Node(0,0);//起点
q.add(node);//入队
while(!q.isEmpty()){
Node tmp = q.poll();//出队
if(tmp.x<0 || tmp.y<0 || tmp.x>=m || tmp.y>=n || vis[tmp.x][tmp.y]) continue;
if(getSum(tmp.x)+getSum(tmp.y)>k) continue;
vis[tmp.x][tmp.y] = true;
cnt++;
for(int i=0;i<4;i++){
q.add(new Node(tmp.x+dx[i],tmp.y+dy[i]));
}
}
return cnt;
}
}
14 I.剪绳子
题目描述:
给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m-1] 。请问 k[0]k[1]…*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
题目地址:https://leetcode-cn.com/problems/jian-sheng-zi-lcof/
我的想法:
注意题目要求的是一根绳子至少被剪成2段。
如果绳子长度≤1就不可能符合要求,因此返回0;
如果绳子长度为2,只能剪成1+1,长度为3,只能剪成1+2;
其他长度,就先看能否剪出长为3的段且剩余长度大于1,再看能否剪出长为2的段且剩余长度大于1,不能满足以上两个条件的说明绳子长度只剩2,其他长度的段(类似4、5…)可以看成是2和3的合成。
class Solution {
public int cuttingRope(int n) {
if(n<=1) return 0;
if(n<4) return n-1;
int mul = 1;
while(n>0){
if(n-3>1){
n -= 3;
mul *= 3;
}else if(n-2>1){
n -= 2;
mul *= 2;
}else{
mul *= n;
break;
}
}
return mul;
}
}
14 II.剪绳子
题目描述:
给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m-1] 。请问 k[0]k[1]…*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
【答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。】
题目地址:https://leetcode-cn.com/problems/jian-sheng-zi-ii-lcof/
我的想法:
与上一题思路一致,但是需要注意取余,取余之前的值可能溢出,所以要用long存储,返回时再强转。
class Solution {
public int cuttingRope(int n) {
if(n<=1) return 0;
if(n<4) return n-1;
long mod = 1000000007;
long mul = 1;
while(n>0){
if(n-3>1){
n -= 3;
mul *= 3;
mul %= mod;
}else if(n-2>1){
n -= 2;
mul *= 2;
mul %= mod;
}else{
mul *= n;
mul %= mod;
break;
}
}
return (int)mul;
}
}
16 数值的整数次方
题目描述:
实现函数double Power(double base, int exponent),求base的exponent次方。不得使用库函数,同时不需要考虑大数问题。
题目地址:https://leetcode-cn.com/problems/shu-zhi-de-zheng-shu-ci-fang-lcof/
我的想法:
用快速幂的方法比较快,需要注意指数可能是-2^31,在取相反数会溢出,需要用long存储指数。
class Solution {
public double myPow(double x, int n) {
if(x==1.0) return x;
if(x==0.0) return 0;
if(n==0) return 1;
if(n==1) return x;
if(n==-1) return 1.0/x;
boolean flg = (n<0?true:false);
long n_ = n;
if(flg) n_ = -n_;
double mul = 1;
double bit = x;
while(n_!=0){
if((n_&1)==1)//当前位是1
mul *= bit;
bit *= bit;
n_ >>= 1;
}
if(flg) return 1.0/mul;
else return mul;
}
}
20 表示数值的字符串
题目描述:
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100"、“5e2”、"-123"、“3.1416”、"-1E-16"、“0123"都表示数值,但"12e”、“1a3.14”、“1.2.3”、"±5"及"12e+5.4"都不是。
题目地址:https://leetcode-cn.com/problems/biao-shi-shu-zhi-de-zi-fu-chuan-lcof/
我的想法:
用HashMap实现自动机的状态迁移,注意状态的定义及转换规则(通过测试用例测出来的规则…)
class Solution {
public enum CharType{
SIGN,//+-符号
NUM,//数字
DOT,//小数点
EXP,//E/e
SPACE,//空格字符
ERROR//错误字符
}
public enum State{
STATE_BEGIN,//初始-状态
STATE_SIGN,//符号-状态
STATE_PRE_NUM,//小数点前面的数字-状态
STATE_DOT,//前面有数字的小数点-状态
STATE_DOT_NOTNUM,//前面没有数字的小数点-状态
STATE_POS_NUM,//小数点后面的数字-状态
STATE_EXP,//e/E符号-状态
STATE_EXP_SIGN,//指数符号-状态
STATE_EXP_NUM,//指数数字-状态
STATE_END//结束状态
}
public CharType checkType(char c){
if(c=='+' || c=='-') return CharType.SIGN;
if(c>='0' && c<='9') return CharType.NUM;
if(c=='E' || c=='e') return CharType.EXP;
if(c=='.') return CharType.DOT;
if(c==' ') return CharType.SPACE;
else return CharType.ERROR;
}
public boolean isNumber(String s) {
final Map<State,Map<CharType,State>> map = new HashMap<>(){
{
//初始状态的转移
put(State.STATE_BEGIN,new HashMap<>(){
{
put(CharType.SIGN,State.STATE_SIGN);//符号
put(CharType.NUM,State.STATE_PRE_NUM);//小数点前数字
put(CharType.DOT,State.STATE_DOT_NOTNUM);//后面接数字的小数点
}});
//符号状态的转移
put(State.STATE_SIGN,new HashMap<>(){
{
put(CharType.NUM,State.STATE_PRE_NUM);//小数点前数字
put(CharType.DOT,State.STATE_DOT_NOTNUM);//无前面数字的小数点
}});
//小数点前的数字状态的转移
put(State.STATE_PRE_NUM,new HashMap<>(){
{
put(CharType.NUM,State.STATE_PRE_NUM);//小数点前数字
put(CharType.DOT,State.STATE_DOT);//有前面数字的小数点
put(CharType.EXP,State.STATE_EXP);//E/e
put(CharType.SPACE,State.STATE_END);//结束
}});
//有前面数字的小数点状态的转移
put(State.STATE_DOT,new HashMap<>(){
{
put(CharType.NUM,State.STATE_POS_NUM);//小数点后的数字
put(CharType.EXP,State.STATE_EXP);//EXP
put(CharType.SPACE,State.STATE_END);//结束
}});
//没有前面数字的小数点状态的转移
put(State.STATE_DOT_NOTNUM,new HashMap<>(){
{
put(CharType.NUM,State.STATE_POS_NUM);//小数点后的数字
}});
//小数点后的数字状态的转移
put(State.STATE_POS_NUM,new HashMap<>(){
{
put(CharType.NUM,State.STATE_POS_NUM);//小数点后的数字
put(CharType.EXP,State.STATE_EXP);//EXP
put(CharType.SPACE,State.STATE_END);//结束
}});
//EXP状态的转移
put(State.STATE_EXP,new HashMap<>(){
{
put(CharType.SIGN,State.STATE_EXP_SIGN);//指数的符号
put(CharType.NUM,State.STATE_EXP_NUM);//指数的数字
}});
//exp符号状态的转移
put(State.STATE_EXP_SIGN,new HashMap<>(){
{
put(CharType.NUM,State.STATE_EXP_NUM);//指数的数字
}});
//exp数字状态的转移
put(State.STATE_EXP_NUM,new HashMap<>(){
{
put(CharType.NUM,State.STATE_EXP_NUM);//指数的数字
put(CharType.SPACE,State.STATE_END);//结束
}});
}};
s = s.trim();
s += ' ';//加一个空格方便判断结束
int len = s.length();
State state = State.STATE_BEGIN;//初始状态
for(int i=0;i<len;i++){
CharType ct = checkType(s.charAt(i));//获取字符类型
//字符类型错误
if(ct==CharType.ERROR) return false;
//字符类型正确则转移状态
if(map.get(state)==null) return false;
if(map.get(state).get(ct)==null) return false;
state = map.get(state).get(ct);
}
if(state==State.STATE_END) return true;
return false;
}
}
26 树的子结构
题目描述:
输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)
B是A的子结构, 即 A中有出现和B相同的结构和节点值。
题目地址:https://leetcode-cn.com/problems/shu-de-zi-jie-gou-lcof/
我的想法:
先找A树中哪个结点的值跟B树的根结点的值相等,然后通过另一个方法递归看左右子树是否相等,遇到不等返回false,然后再继续找A树的结点。即:先找根,再对点。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public boolean check(TreeNode A,TreeNode B){
if(B==null) return true;//已经找完
if(A==null || A.val!=B.val) return false;
return check(A.left,B.left) && check(A.right,B.right);
}
public boolean isSubStructure(TreeNode A, TreeNode B) {
if(A==null || B==null) return false;
//根结点匹配则查找剩余结点
if(A.val==B.val && check(A,B)) return true;
//不匹配就继续找
return isSubStructure(A.left,B) || isSubStructure(A.right,B);
}
}
31 栈的压入、弹出序列
题目描述:
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。
题目地址:https://leetcode-cn.com/problems/zhan-de-ya-ru-dan-chu-xu-lie-lcof/
我的想法:
利用Java的Stack类,模拟栈的压入、弹出过程,如果遇到栈顶与popped数组的索引 j 对应的值就弹出,如果最终栈还有数字说明顺序不符合。
class Solution {
public boolean validateStackSequences(int[] pushed, int[] popped) {
if(pushed.length!=popped.length) return false;
int len = pushed.length;
Stack<Integer> s = new Stack<>();
int j = 0;
for(int i=0;i<len;i++){
s.push(pushed[i]);
while(!s.empty() && s.peek()==popped[j] && j<len){
s.pop();
j++;
}
}
if(!s.empty()) return false;
return true;
}
}
32 从上到下打印二叉树 III
题目描述:
请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。
题目地址:https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof/
我的想法:
用一个队列存储每层的结点,每次都倒序取出,注意左子结点和右子结点的放入顺序。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> list = new LinkedList<>();
if(root==null) return list;
Deque<TreeNode> q = new LinkedList<>();
q.add(root);
boolean drc = true;
while(!q.isEmpty()){
int size = q.size();
List<Integer> l = new LinkedList<>();
Deque<TreeNode> tmp = new LinkedList<>();
for(int i=0;i<size;i++){
TreeNode node = q.removeLast();
if(drc){
if(node.left!=null) tmp.add(node.left);
if(node.right!=null) tmp.add(node.right);
}else{
if(node.right!=null) tmp.add(node.right);
if(node.left!=null) tmp.add(node.left);
}
l.add(node.val);
}
drc = !drc;
list.add(l);
q = null;q = tmp;
}
return list;
}
}
33 二叉搜索树的后序遍历序列
题目描述:
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。
题目地址:https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/
我的想法:
后序遍历的元素顺序是:左-右-根,每次从两个方向检查,从左到右遍历【左子树部分】时,遇到的数字一定都小于根元素的值,从右到左遍历【右子树部分】时,遇到的数字一定都大于根元素的值(题目已说明元素不重复,所以不考虑相等),最后比较遍历两部分结束时的索引是不是相接的,不是的话说明不是二叉搜索树。
class Solution {
public boolean check(int[] postorder,int left,int right){
if(left>=right) return true;
int i = left,j = right-1;
while(i<=right && postorder[i]<postorder[right]) i++;
while(j>=left && postorder[j]>postorder[right]) j--;
if((i-1)!=j) return false;
return check(postorder,left,i-1) && check(postorder,i,right-1);
}
public boolean verifyPostorder(int[] postorder) {
int len = postorder.length;
if(len<=1) return true;
return check(postorder,0,len-1);
}
}
34 二叉树中和为某一值的路径
题目描述:
输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。
题目地址:https://leetcode-cn.com/problems/er-cha-shu-zhong-he-wei-mou-yi-zhi-de-lu-jing-lcof/
我的想法:
按照dfs的做法,题目并没有说所有值都是正数,所以不能用剪枝的方法(遇到>sum立即返回)。
【注意!!】每次走到符合要求的叶子结点后要将list放进二维list里,要用new LinkedList<>(list)的方法,不能直接list.add(list),因为放进去的是引用,后续会被改变值。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public void find(List<List<Integer>> list,List<Integer> l,TreeNode root,int sum,int cnt){
if(root==null) return ;
if(root.left==null && root.right==null && cnt+root.val==sum){
//到达路径末尾
l.add(root.val);
list.add(new LinkedList<Integer>(l));
}
//还未到达路径末尾
else{
l.add(root.val);
find(list,l,root.left,sum,cnt+root.val);
find(list,l,root.right,sum,cnt+root.val);
}
l.remove(l.size()-1);
}
public List<List<Integer>> pathSum(TreeNode root, int sum) {
List<List<Integer>> list = new LinkedList<>();
List<Integer> l = new LinkedList<>();
find(list,l,root,sum,0);
return list;
}
}
35 复杂链表的复制
题目描述:
请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。
题目地址:https://leetcode-cn.com/problems/fu-za-lian-biao-de-fu-zhi-lcof/
我的想法:
用HashMap存储原结点及其副本结点,这样一次顺序遍历复制后就可以通过(原结点,副本结点)→(原结点的random结点,副本结点的random结点),需要两次顺序遍历,一次复制顺序连接关系,另一次复制random指向关系。
/*
// 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;//头结点为空
HashMap<Node,Node> map = new HashMap<>();
Node tmp = new Node(head.val);//复制头结点
Node s = tmp,p = head.next;
map.put(head,tmp);
//顺序复制
while(p!=null){
tmp = new Node(p.val);//复制结点
s.next = tmp;
s = tmp;
map.put(p,tmp);
p = p.next;
}
//复制random关系
p = head;
while(p!=null){
Node dpc = map.get(p);//获得副本结点
Node dpc_random = map.get(p.random);//获得random结点的副本结点
dpc.random = dpc_random;
p = p.next;
}
return map.get(head);
}
}
36 二叉搜索树与双向链表
题目描述:
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。
题目地址:https://leetcode-cn.com/problems/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof/
我的想法:
二叉搜索树的中序遍历序列就是按照自然顺序排列的,在访问root.left和root.right中间去操作结点,操作的次序就是按照从小到大顺序。
/*
// Definition for a Node.
class Node {
public int val;
public Node left;
public Node right;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val,Node _left,Node _right) {
val = _val;
left = _left;
right = _right;
}
};
*/
class Solution {
private Node head,pre;
public void dfs(Node cur){
if(cur==null) return ;
dfs(cur.left);
if(head==null) head = cur;//首次标记头结点
if(pre!=null) pre.right = cur;//把前面结点的right指针指向当前结点
cur.left = pre;//把当前结点的left指着指向前面的结点
pre = cur;
dfs(cur.right);
}
public Node treeToDoublyList(Node root) {
if(root==null) return null;
head = pre = null;
dfs(root);
head.left = pre;
pre.right = head;
return head;
}
}
44 数字序列中某一位的数字
题目描述:
数字以0123456789101112131415…的格式序列化到一个字符序列中。在这个序列中,第5位(从下标0开始计数)是5,第13位是1,第19位是4,等等。
请写一个函数,求任意第n位对应的数字。
题目地址:https://leetcode-cn.com/problems/shu-zi-xu-lie-zhong-mou-yi-wei-de-shu-zi-lcof/
我的想法:
先求出该下标对应的数字是哪一个,再求出对应的数位是哪一个,然后取出该数位对应的数字即可。过程比较麻烦。规律是:
1~9 (共占9×1位)(0可以忽略是因为下标是从0开始计算的)
10~99 (共占90×2位)
100~999 (共占900×3位)
以此类推…
class Solution {
public int findNthDigit(int n) {
if(n<10) return n;
long bits = 1,cnt = 9,pos = (long)n,base = 1;
while((pos-bits*cnt)>0){
pos -= (cnt*bits);
bits++;
cnt *= 10;
base *= 10;
}
long bit;//第n个数字的第几位
if(pos%bits==0){
pos = pos-1;
bit = (int)bits;
}else{
bit = pos%bits;
}
long num = pos/bits + base;//对应的数字
return String.valueOf(num).toCharArray()[(int)bit-1]-48;
}
}
38 字符串的排列
题目描述:
输入一个字符串,打印出该字符串中字符的所有排列。
你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。
题目地址:https://leetcode-cn.com/problems/zi-fu-chuan-de-pai-lie-lcof/
我的想法:
初看题目便知道需要使用回溯的解法,但是我不是很懂如果字符串有重复字母时应该怎么处理。参考了题解,思路是:增加一个排序的步骤,假设str[0]和str[1]为相同字母,在拼凑字符串时如果在某一个位置需要使用str[1],那么必须保证str[0]要被使用过,这样可以保证存在相同字母时某个位置只能用一次(即排序后的第一个字母)。
同时学到一个新写法,list转为String数组:
list.toArray(new String[0])
class Solution {
private int len;
private char[] str;
private List<String> list;
private boolean[] vis;
private StringBuilder sb;
public void dfs(int idx){
if(idx==len){
list.add(sb.toString());
return ;
}
for(int i=0;i<len;i++){
if(i>0 && str[i]==str[i-1] && !vis[i-1] && !vis[i]) continue;
if(!vis[i]){
vis[i] = true;
sb.append(str[i]);
dfs(idx+1);
sb.deleteCharAt(sb.length()-1);
vis[i] = false;
}
}
}
public String[] permutation(String s) {
list = new LinkedList<>();//存储结果
str = s.toCharArray();//转为字符数组
Arrays.sort(str);//数组排序
len = str.length;//字符串长度
vis = new boolean[len];//是否被访问
sb = new StringBuilder();//字符串构造
dfs(0);
return list.toArray(new String[0]);
}
}
同样的,我们可以解决第47题:带重复数字的数组全排列。题目地址:https://leetcode-cn.com/problems/permutations-ii/
class Solution {
private List<List<Integer>> list;
private boolean[] vis;
private int len;
private List<Integer> arr;
public void dfs(int depth,int[] nums){
if(depth==len){
list.add(new LinkedList<>(){
{
addAll(arr);}});//加入结果集
return ;
}
for(int i=0;i<len;i++){
if(vis[i]) continue;
if(i>0 && nums[i]==nums[i-1] && !vis[i-1]) continue;
arr.add(nums[i]);
vis[i] = true;
dfs(depth+1,nums);
vis[i] = false;
arr.remove(arr.size()-1);
}
}
public List<List<Integer>> permuteUnique(int[] nums) {
list = new LinkedList<>();//结果集
arr = new LinkedList<>();//单个列表
Arrays.sort(nums);//排序
len = nums.length;//数组长度
vis = new boolean[len];//访问数组
dfs(0,nums);//回溯
return list;
}
}
45 把数组排成最小的数
题目描述:
输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。
题目地址:https://leetcode-cn.com/problems/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof/
我的想法:
从题目可以知道一定是需要对数组进行排序的,如果对于类似数组[1,2,3,4],那组成的数字一定是首位较小的排在前面,才能让数字小。但是存在特殊情况使排序不能只按照字符串排序规则进行,例如对于数组[1,10,2],排序之后元素“1”一定在元素“10”前面,那组成的数字就是“1102”,但其实还能组成更小的数字“1012”,这是因为“10”后面有个0使它可以与“1”组合后形成“101”而不是“110”。
这里的写法参考了一个月前阅读过的题解,要对字符串线组合后排序,对于上述“1”和“10”的例子,如果“1”和“10”两种拼接组合“110”和“101”,如果“101”小于“110”,则“10”应该排在“1”前面,否则就是后面。
同时学到一个新写法,利用Collections.sort()排序集合类:
Collections.sort(list,new Comparator<String>
(){
@Override
public int compare(String s1,String s2){
return new String(s1+s2).compareTo(s2+s1);
}
})
class Solution {
public String minNumber(int[] nums) {
List<String> list = new LinkedList<>();
for(int num:nums)
list.add(String.valueOf(num));
Collections.sort(list,new Comparator<String>(){
@Override
public int compare(String a,String b){
String a_b = a+b;
String b_a = b+a;
return a_b.compareTo(b_a);
}
});
StringBuilder sb = new StringBuilder();
for(String str:list)
sb.append(str);
return sb.toString();
}
}
45 把数组排成最小的数
题目描述:
给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。
题目地址:https://leetcode-cn.com/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof/
我的想法:
类似于dfs的做法,如果当前数字与下一位数字组成的数字范围在10~25以内,步长就以2自加。
class Solution {
private int cnt = 0;
public void dfs(int idx,char[] nums){
if(idx>=nums.length-1){
cnt++; return ;
}
dfs(idx+1,nums);
if((idx+1)<nums.length && (nums[idx]=='1' || (nums[idx]=='2' && nums[idx+1]<='5')))
dfs(idx+2,nums);
}
public int translateNum(int num) {
char[] nums = String.valueOf(num).toCharArray();
dfs(0,nums);
return cnt;
}
}
47 把数组排成最小的数
题目描述:
在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?
题目地址:https://leetcode-cn.com/problems/li-wu-de-zui-da-jie-zhi-lcof/
我的想法:
比较清晰简单容易看出递推关系的动态规划,既然路径只能往右或者往下,那么对于一个单元格来说,在它之前的礼物总价值只能通过其上方或者左边的单元格计算得到,找到该单元格上面或左边的最大值即可。
class Solution {
public int maxValue(int[][] grid) {
int rows = grid.length;
if(rows==0) return 0;
int cols = grid[0].length;
if(cols==0) return 0;
int[][] dp = new int[rows][cols];
//对dp数组的第一列、第一行赋值
for(int i=0;i<cols;i++){
if(i>0)dp[0][i] = dp[0][i-1] + grid[0][i];
else dp[0][i] = grid[0][i];
}
for(int i=1;i<rows;i++)
dp[i][0] = dp[i-1][0] + grid[i][0];
for(int i=1;i<rows;i++){
for(int j=1;j<cols;j++){
dp[i][j] = grid[i][j] + (dp[i-1][j]>dp[i][j-1]?dp[i-1][j]:dp[i][j-1]);
}
}
return dp[rows-1][cols-1];
}
}
48 最长不含重复字符的子字符串
题目描述:
请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。
题目地址:https://leetcode-cn.com/problems/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof/
我的想法:
dp数组中的dp[i]用来维护一个不重复字符串的窗口范围,dp[i]=j代表s[j]到s[i]没有出现重复字符,dp[i+1]则需要扫描s[dp[i]]到s[i]有没有重复字符,有的话该窗口的前沿后退一格。一开始在想有没有O(n)的算法,但是感觉应该没有。
class Solution {
public int lengthOfLongestSubstring(String s) {
if(s.length()==0 || s==null) return 0;
int[] dp = new int[s.length()];
dp[0] = 0;
for(int i=1;i<dp.length;i++){
int idx = -1;
for(int j=dp[i-1];j<i;j++)
if(s.charAt(i)==s.charAt(j)){
idx = j;break;
}
if(idx!=-1) dp[i] = idx + 1;
else dp[i] = dp[i-1];
}
int maxv = 0;
for(int i=0;i<dp.length;i++)
if((i-dp[i]+1)>maxv) maxv = (i-dp[i]+1);
return maxv;
}
}
49 丑数
题目描述:
我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。
题目地址:https://leetcode-cn.com/problems/chou-shu-lcof/
我的想法:
一开始想的是,每次从三个序列:
(2×1,2×2,2×3,2×4,2×5,2×6,2×7,2×8,…)
(3×1,3×2,3×3,3×4,3×5,3×6,3×7,3×8,…)
(5×1,5×2,5×3,5×4,5×5,5×6,5×7,5×8,…)
中依次挑选出最小值,每次用三个指针标记位置,但其实这样不妥,因为比如2×7这个数字,因子包括7,不符合题意。
所以三个指针应该指向已经取过的数字(然后再去×2,3,5),避免2×7,2×11这种情况发生。
class Solution {
public int nthUglyNumber(int n) {
if(n==1) return 1;
int[] dp = new int[n];
dp[0] = 1;
int f2,f3,f5;
f2 = f3 = f5 = 0;
int cur = 1;
for(int i=1;i<n;i++){
cur = Math.min(dp[f2]*2,Math.min(dp[f3]*3,dp[f5]*5));
if(cur==dp[f2]*2) f2++;
if(cur==dp[f3]*3) f3++;
if(cur==dp[f5]*5) f5++;
dp[i] = cur;
}
return cur;
}
}
56 I. 数组中数字出现的次数
题目描述:
一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
题目地址:https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof/
我的想法:
题目要采用位运算,因为出现两个只出现一次的数字,而int一般是32位,那么就必定存在某一位上,数组中所有数字在该位上的数字为0或1,且0或者1的个数是奇数个,那么就可以根据这个将数组划分成两部分(一部分包含偶数个其他数字和一个奇数个数字,另一部分也包含偶数个其他数字和一个奇数个数字),再运用或运算筛选出数字。
例如:对于测试用例[4,1,4,6],它们的二进制是[0100,0001,0100,0110],从右到左的第1位是1且总的1的个数是奇数个,就可以将数组分为[0001]和[0100,0100,0110]。
class Solution {
public int[] singleNumbers(int[] nums) {
int len = nums.length;
int bit = 1;//数位筛选
for(int i=0;i<32;i++){
//对每一个数字的32个位都运算
int cnt = 0;
for(int j=0;j<len;j++)//遍历数组中每一个数字
if((nums[j] & bit)==bit) cnt++;
if(cnt%2==1) break;
bit <<= 1;//筛选位左移
}
int a = -1,b = -1;//两个需要筛选出来的数字
for(int i=0;i<len;i++){
//分组
if((nums[i]&bit)==0){
if(a==-1) a = nums[i];
else a ^= nums[i];
}else{
if(b==-1) b = nums[i];
else b ^= nums[i];
}
}
return new int[]{
a,b};
}
}
56 II. 数组中数字出现的次数 II
题目描述:
在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。
题目地址:https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-ii-lcof/
我的想法:
同属于位运算题目,因为其他数字出现了3次,所以每一位数字可以统计出现0/1的总数,如果出现某位上1的总次数模3等于1的情况,说明我们要找的那个数字在该位上是1,用一个数组存储各位上出现1的次数是否整除3,然后再加起来(题目说了所有数字都是正整数所以不考虑符号的作用)。
class Solution {
public int singleNumber(int[] nums) {
int bit = 1;//用于筛选的数位
boolean[] bits = new boolean[32];
for(int i=31;i>=0;i--){
int cnt = 0;//统计当前位上1的数量
for(int j=0;j<nums.length;j++)
if((nums[j]&bit)==bit) cnt++;
if(cnt%3==0) bits[i] = false;
else bits[i] = true;
bit <<= 1;
}
int sum = 0;
int mul = 1;
for(int i=31;i>=0;i--){
if(bits[i]) sum += mul;
if(i!=0) mul *= 2;
}
return sum;
}
}
59 II. 队列的最大值
题目描述:
请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。
若队列为空,pop_front 和 max_value 需要返回 -1
题目地址:https://leetcode-cn.com/problems/dui-lie-de-zui-da-zhi-lcof/
我的想法:
用一个普通队列负责pop和push的职能,再用另一个双端队列保持一个单调不增序列。
class MaxQueue {
private Queue<Integer> q;
private Deque<Integer> axg;
public MaxQueue() {
q = new LinkedList<>();
axg = new LinkedList<>();
}
public int max_value() {
if(q.isEmpty()) return -1;
if(axg.isEmpty()) return q.peek();
return axg.peek();
}
public void push_back(int value) {
q.add(value);
while(!axg.isEmpty() && axg.peek()<value) axg.removeFirst();
while(!axg.isEmpty() && axg.peekLast()<value) axg.removeLast();
axg.addLast(value);
}
public int pop_front() {
if(q.isEmpty()) return -1;
int tmp = q.poll();
if(axg.peek()==tmp) axg.poll();
return tmp;
}
}
/**
* Your MaxQueue object will be instantiated and called as such:
* MaxQueue obj = new MaxQueue();
* int param_1 = obj.max_value();
* obj.push_back(value);
* int param_3 = obj.pop_front();
*/
60 n个骰子的点数
题目描述:
把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。
你需要用一个浮点数数组返回答案,其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i 小的那个的概率。
题目地址:https://leetcode-cn.com/problems/nge-tou-zi-de-dian-shu-lcof/
我的想法:
用一个类似于动态规划的数组,dp[i][j]表示第i次投骰子时点数总和为j的概率,那么dp[i][j]=dp[i-1][j-k]*0.16667(k∈[1,6]且(j-k)>0)。
class Solution {
public double[] dicesProbability(int n) {
double[][] arr = new double[n+1][6*n+1];
double[] res = new double[5*n+1];
//先初始化第一次扔骰子的概率
for(int i=1;i<6*n+1;i++){
if(i<=6) arr[1][i] = 1.0/6;
else arr[1][i] = 0;
}
for(int i=2;i<=n;i++){
//第i次扔骰子点数之和的范围是i~6*i
for(int j=i;j<=6*i;j++){
for(int k=1;k<=6 && (j-k)>0;k++)
arr[i][j] += arr[i-1][j-k] * (1.0/6);
}
}
int idx = 0;
for(int i=n;i<=6*n;i++)
res[idx++] = arr[n][i];
return res;
}
}
63 股票的最大利润
题目描述:
假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?
题目地址:https://leetcode-cn.com/problems/gu-piao-de-zui-da-li-run-lcof/
我的想法:
动态规划,转移方程看下放。dp[i]=j表示第i天卖出的股票是第j天买入的。
class Solution {
public int maxProfit(int[] prices) {
if(prices.length==1 || prices.length==0) return 0;
int[] dp = new int[prices.length];
dp[0] = 0;//dp[i]=j表示第i天卖出的股票是在第j天买入的
for(int i=1;i<dp.length;i++){
if(prices[i]<prices[dp[i-1]])
dp[i] = i;
else
dp[i] = dp[i-1];
}
int maxn = 0;
for(int i=1;i<dp.length;i++){
if(prices[i]-prices[dp[i]]>maxn)
maxn = prices[i] - prices[dp[i]];
}
return maxn;
}
}
64 求1+2+…+n
题目描述:
求 1+2+…+n ,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
题目地址:https://leetcode-cn.com/problems/qiu-12n-lcof/
我的想法:
用&&运算符实现递归。
class Solution {
boolean flg = false;
int sum = 0;
public int sumNums(int n) {
flg = ((n>0) && (n += sumNums(n-1))>0);
return n;
}
}
66 构建乘积数组
题目描述:
给定一个数组 A[0,1,…,n-1],请构建一个数组 B[0,1,…,n-1],其中 B[i] 的值是数组 A 中除了下标 i 以外的元素的积, 即 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。不能使用除法。
题目地址:https://leetcode-cn.com/problems/gou-jian-cheng-ji-shu-zu-lcof/
我的想法:
用两个数组存中间运算结果,一个从左往右乘,一个从右往左乘。
class Solution {
public int[] constructArr(int[] a) {
if(a.length==0) return new int[]{
};
int[] left = new int[a.length];
int[] right = new int[a.length];
int[] res = new int[a.length];
left[0] = a[0];
right[a.length-1] = a[a.length-1];
for(int i=1;i<a.length;i++){
left[i] = left[i-1] * a[i];
right[a.length-i-1] = right[a.length-i] * a[a.length-i-1];
}
for(int i=1;i<a.length-1;i++)
res[i] = left[i-1] * right[i+1];
res[0] = right[1];
res[a.length-1] = left[a.length-2];
return res;
}
}
67 把字符串转换成整数
题目描述:
写一个函数 StrToInt,实现把字符串转换成整数这个功能。不能使用 atoi 或者其他类似的库函数。
首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。
当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。
该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。
注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。
在任何情况下,若函数不能进行有效的转换时,请返回 0。
说明:
假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−231, 231 − 1]。如果数值超过这个范围,请返回 INT_MAX (231 − 1) 或 INT_MIN (−231) 。
题目地址:https://leetcode-cn.com/problems/ba-zi-fu-chuan-zhuan-huan-cheng-zheng-shu-lcof/
我的想法:
用自动机状态转移判断字符串是否合法,然后截取符合 要求的那部分去计算数字,为了防止溢出边缘计算时int溢出所以中间结果用long存储。
class Solution {
public enum CharType{
CHAR_SIGN,
CHAR_NUM,
CHAR_ERROR
};
private enum State{
STATE_BEGIN,
STATE_SIGN,
STATE_NUM,
STATE_END,
STATE_ERROR
};
public CharType getType(char c){
if(c=='+' || c=='-') return CharType.CHAR_SIGN;
if(c>='0' && c<='9') return CharType.CHAR_NUM;
else return CharType.CHAR_ERROR;
}
public int getInt(char[] ch,int left,int right){
long num = 0,base = 1;
boolean flg = true;
if(ch[left]=='+') left++;
else if(ch[left]=='-'){
flg = false;left++;}
for(int i=left;i<=right;i++){
if(ch[i]=='0') left++;
else break;
}
for(int i=right;i>=left;i--){
num += (ch[i]-48)*base;
if(!flg && (-num<-2147483648 || base>1000000000)) return -2147483648;
if(flg && (num>2147483647 || base>1000000000)) return 2147483647;
base *= 10;
}
return (flg?(int)num:(int)-num);
}
public int strToInt(String str) {
char[] s = str.trim().toCharArray();//去除空格
if(s.length==0) return 0;
Map<State,Map<CharType,State>> map = new HashMap<>();
//初始状态转移
map.put(State.STATE_BEGIN,new HashMap<>(){
{
put(CharType.CHAR_SIGN,State.STATE_SIGN);
put(CharType.CHAR_NUM,State.STATE_NUM);
put(CharType.CHAR_ERROR,State.STATE_ERROR);
}});
//符号状态转移
map.put(State.STATE_SIGN,new HashMap<>(){
{
put(CharType.CHAR_NUM,State.STATE_NUM);
put(CharType.CHAR_ERROR,State.STATE_ERROR);
}});
//数字状态转移
map.put(State.STATE_NUM,new HashMap<>(){
{
put(CharType.CHAR_NUM,State.STATE_NUM);
put(CharType.CHAR_ERROR,State.STATE_END);
put(CharType.CHAR_SIGN,State.STATE_END);
}});
State state = State.STATE_BEGIN;//初始状态
int i;
for(i=0;i<s.length;i++){
CharType type = getType(s[i]);//获得字符类型
state = map.get(state).get(type);//当前字符应该是什么状态
if(state==null) return 0;
if(state==State.STATE_ERROR) return 0;
if(state==State.STATE_END) break;
}
long num = getInt(s,0,i-1);
return (int)num;
}
}
34 二叉树中和为某一值的路径
题目描述:
输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。
题目地址:https://leetcode-cn.com/problems/er-cha-shu-zhong-he-wei-mou-yi-zhi-de-lu-jing-lcof/
我的想法:
dfs,顺带记住一个深复制list的写法:list1.add(new LinkedList(list2));
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
private List<List<Integer>> list;
public void dfs(TreeNode root,int target,int sum,List<Integer> l){
if(root==null) return ;
if(root.left==null && root.right==null){
l.add(root.val);
if(sum+root.val==target)
list.add(new LinkedList(l));//复制列表
l.remove(l.size()-1);
return ;
}
l.add(root.val);
if(root.left!=null)
dfs(root.left,target,sum+root.val,l);
if(root.right!=null)
dfs(root.right,target,sum+root.val,l);
l.remove(l.size()-1);
}
public List<List<Integer>> pathSum(TreeNode root, int target) {
list = new LinkedList<>();
List<Integer> l = new LinkedList<>();
dfs(root,target,0,l);
return list;
}
}
剑指offer中等题二刷完毕,接下来准备按顺序刷leetcode的1~200题。