赞
踩
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题。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。