此题属于力扣中等难度题,初次做此题实在有难度,还是自己太菜了555555。不看题解实在是写不出来
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
HashMap map = new HashMap<>();
public TreeNode buildTree(int[] preorder, int[] inorder) {
for(int i = 0; i < inorder.length; i++)
map.put(inorder[i], i);
return recur(preorder,0, 0, inorder.length - 1);
}
TreeNode recur(int [] preorder,int root, int left, int right) {
if(left > right) return null; // 递归终止
TreeNode node = new TreeNode(preorder[root]); // 建立根节点
int i = map.get(preorder[root]); // 划分根节点、左子树、右子树
node.left = recur(preorder,root + 1, left, i - 1); // 开启左子树递归
node.right = recur(preorder,root + i - left + 1, i + 1, right); // 开启右子树递归
return node; // 回溯返回根节点
}
}
代码思路:
首先需要了解中序遍历和前序遍历的基本原理。
然后根据前序遍历的数组的第一个值即为根节点,然后通过此根节点查找中序遍历数组中对应的索引,这里为了减少时间复杂度,引入了哈希map,也就是先利用中序数组new一个hashmap,再得到中序数组中的根节点索引。
再定义一个递归调用的函数,首先确定递归终止条件:
即中序数组的左边界大于右边界。
再建立根节点,确定中序数组中的根索引,从而确定划分左右子树。然后开始递归调用,即确定左子树:node.left = recur()
右子树:node.right = recur(),主要是要搞清楚里面的参数代表的意思,如:前序数组,前序数组的根索引,中序数组的左边界,中序数组的右边界
先放代码:
class CQueue {
Stack stackA;
Stack stackB;
public CQueue() {
stackA = new Stack();
stackB = new Stack();
}
public void appendTail(int value) {
stackA.push(value);
}
public int deleteHead() {
if (stackB.isEmpty()){
if(stackA.isEmpty()){
return -1;
}
while(!stackA.isEmpty()){
stackB.push(stackA.pop());
}
}
return stackB.pop();
}
}
/**
* Your CQueue object will be instantiated and called as such:
* CQueue obj = new CQueue();
* obj.appendTail(value);
* int param_2 = obj.deleteHead();
*/
此题需要注意的就是分清楚两个栈的各自作用,其中A的作用就是入栈即入队操作,B的作用就是出队的操作,不过此处需要注意的就是,在出队操作时,需要首先判断B是否为空,若为空,则再进行判断A,若也为空,则返回-1;若A不为空,则将A中的元素弹出到B,顺序刚好就反转回来了,然后再弹出B的元素;若B不为空,则弹出B的元素即为出队元素。
还是先上代码把
class MyStack {
Queue queue1;
Queue queue2;
public MyStack() {
queue1 = new LinkedList();
queue2 = new LinkedList();
}
public void push(int x) {
queue2.offer(x);
while (!queue1.isEmpty()) {
queue2.offer(queue1.poll());
}
Queue temp = queue1;
queue1 = queue2;
queue2 = temp;
}
public int pop() {
return queue1.poll();
}
public int top() {
return queue1.peek();
}
public boolean empty() {
return queue1.isEmpty();
}
}
队列1作为主队列,队列2作为辅助队列。当入栈时,首先将元素放入队列2,然后将队列1中的元素全部出队放入队列2中,这样子就满足了后进的在最底部,即后进先出的原则,然后将队列1和2互换,再将队列1进行出队操作,就属于后进先出的栈弹出操作了。
老规矩先上代码:
class Solution {
public int fib(int n) {
if(n == 0) return 0;
int[] dp = new int[n + 1];
dp[0] = 0;
dp[1] = 1;
for(int i = 2; i <= n; i++){
dp[i] = dp[i-1] + dp[i-2];
dp[i] %= 1000000007;
}
return dp[n];
}
}
此问题虽然是典型的递归教科书讲解题,但是如果直接用递归来写的话,时间效率太低,当次数太高时,会有很多重复计算,因此,可以使用动态规划思想,将问题化为各个子问题,然后根据自底向上的循环,依次计算求值,注意到这儿使用了一个数组保存之前的值,提高了效率。
代码:
class Solution {
public int numWays(int n) {
if(n<=1){
return 1;
}
if(n==2){
return 2;
}
int []s = new int [n+1];
s[0] = 1;
s[1] = 1;
s[2] = 2;
for(int i = 3;i<=n;i++){
s[i] = (s[i-1] +s[i-2]) % 1000000007;
}
return s[n];
}
}
这个可是双百beats哦,还是不错了。
解题思路:
其实此题就是求斐波拉契数列的封装。最关键的就是要明白青蛙跳最后一步的时候,要么跳一级台阶,要么跳两级台阶,因此可化为跳n级台阶的时候f(n) = f(n-1)+f(n-2)。借鉴动态规划的思想,从下往上循环,避免使用递归重复计算,提高效率,同时使用一个数组进行保存之前的值,减少多个变量的定义,提高效率。还有就是初始值需要单独列出来,跟斐波拉契的初始值有一丢丢区别。
快排是20世纪十大最伟大的算法之一,可见其重要性了。因此很多公司面试时,会经常考察这个题。其中最需要注意的就是,此算法涉及到递归和指针两个知识点。在交换数据顺序的时候,可以采用单边循环法或者双边循环法,而因为单边循环法只需要一个指针作指引,且代码简单得多。因此,这里着重记录单边循环法。主要看partition函数,只有一个for循环,用于寻找比基准元素小的元素,然后移动mark索引,并交换两个元素。循环完后再将基准元素和mark索引对应的元素进行交换,并返回基准元素索引mark。代码如下:
class Solution{
public static void quickSort(int[] arr, int startIndex,
int endIndex) {
// 递归结束条件:startIndex大于或等于endIndex时
if (startIndex >= endIndex) {
return;
}
// 得到基准元素位置
int pivotIndex = partition(arr, startIndex, endIndex);
// 根据基准元素,分成两部分进行递归排序
quickSort(arr, startIndex, pivotIndex - 1);
quickSort(arr, pivotIndex + 1, endIndex);
}
/**
* 分治(单边循环法)
* @param arr 待交换的数组
* @param startIndex 起始下标
* @param endIndex 结束下标
*/
private static int partition(int[] arr, int startIndex,
int endIndex) {
// 取第1个位置(也可以选择随机位置)的元素作为基准元素
int pivot = arr[startIndex];
int mark = startIndex;
for(int i=startIndex+1; i<=endIndex; i++){
if(arr[i]
此题最简单最直接的做法,可能就是直接来个遍历,找出最小的值,在力扣上也试过这种做法,奇怪的是,本来时间复杂度为O(n),效率不算高,但是结果确实击败了100%,估计是测试的例子刚好没遇到极端的情况吧。此题虽然是简单题,但想要优化时间复杂度,降为logN,需要用到二分查找法,涉及到对两个指针的操作,还是有一点难度的,至少不看题解,我是想不到的。先放代码:
class Solution {
public int minArray(int[] numbers) {
int i = 0, j = numbers.length - 1;
while (i < j) {
int m = (i + j) / 2;
if (numbers[m] > numbers[j]) i = m + 1;
else if (numbers[m] < numbers[j]) j = m;
else j--;
}
return numbers[i];
}
}
其中两个指针为i,j,用来寻找旋转分界点。这道题要利用旋转数组的特性,也就是左边数组值肯定大于右边数组值。因此,判断二分中界点m的位置很关键。当nums[m]>nums[j]时,m肯定在左边数组,因此可将i缩小范围,令i=m+1;当nums[m]
还有一种特殊情况:当nums[m]==nums[j],可以证明左边数组或者右边数组都相等。**此时可跳出二分查找,直接使用线性查找,即遍历,更快。**代码如下:
class Solution {
public int minArray(int[] numbers) {
int i = 0, j = numbers.length - 1;
while (i < j) {
int m = (i + j) / 2;
if (numbers[m] > numbers[j]) i = m + 1;
else if (numbers[m] < numbers[j]) j = m;
else {
int x = i;
for(int k = i + 1; k < j; k++) {
if(numbers[k] < numbers[x]) x = k;
}
return numbers[x];
}
}
return numbers[i];
}
}
阿西,此题好难,看题解都要看好半天才能理解。害。。。。。
言归正传,本题主要涉及到递归。看题解说此题涉及到–深度优先搜索(DFS)+剪枝。这啥玩意儿,戴个这么高深的术语帽子,来吓人,是生怕我们看懂嘛,哼╭(╯^╰)╮
还是先放代码把,结合代码来看容易多了:
class Solution {
public boolean exist(char[][] board, String word) {
char[] words = word.toCharArray();
for(int i = 0; i < board.length; i++) {
for(int j = 0; j < board[0].length; j++) {
if(dfs(board, words, i, j, 0)) return true;
}
}
return false;
}
boolean dfs(char[][] board, char[] word, int i, int j, int k) {
if(i >= board.length || i < 0 || j >= board[0].length || j < 0 || board[i][j] != word[k]) return false;
if(k == word.length - 1) return true;
board[i][j] = '\0';
boolean res = dfs(board, word, i + 1, j, k + 1) || dfs(board, word, i - 1, j, k + 1) ||
dfs(board, word, i, j + 1, k + 1) || dfs(board, word, i , j - 1, k + 1);
board[i][j] = word[k];
return res;
}
}
说白了,对于一个矩阵,先弄两个for循环,以遍历所有的元素,也就是看所有的字符路径是否有符合要求的路径。然后对于每一次搜索,需要知道四个参数:原矩阵,目标字符数组,矩阵中的元素索引i和j,也就是第几行第几列,和目标字符索引k。对于DFS搜索函数,其实是个递归调用函数,首先我们需要知道递归调用终止条件:数组越界,即i和j<0或者>=数组长度或者不等于目标字符,则return false;当继续执行下一步时,说明当前访问字符与目标字符相等,于是需要判断k是否等于目标字符数组的最后一个字符索引,若等于的话,则立即返回return true;然后为了避免对访问后的字符重复进行访问,我们将其修改为board[i][j] = ‘\0’,然后再以下、上、右、左的顺序进行下一步搜索,也就是递归调用搜索函数,只要搜索到一条可行路径即可,所以用或||连接,然后将访问过的字符进行恢复,**即board[i][j] = word[k];这一步也很关键,不然原矩阵会被改变,影响下一次的搜索。**函数中数组作为参数的 时候,是传递的 引用,所有一改会跟着改。
先上代码:
class Solution {
public int movingCount(int m, int n, int k) {
boolean[][] visited = new boolean[m][n];
return dfs(visited, m, n, k, 0, 0);
}
private int dfs(boolean[][] visited, int m, int n, int k, int i, int j) {
if(i >= m || j >= n || visited[i][j] || bitSum(i) + bitSum(j) > k) return 0;
visited[i][j] = true;
return 1 + dfs(visited, m, n, k, i + 1, j) + dfs(visited, m, n, k, i, j + 1) ;
}
private int bitSum(int n) {
int sum =0;
if (n<10) sum= n;
else if(n==100) sum =1;
else sum = n%10+n/10 ;
return sum;
}
}
同样是不看题解不会做系列,啊啊啊啊啊啊啊啊,烦
此题属于路径搜索问题,同样可以使用递归的方法,采用深度优先搜索(DFS),其中关键的就是需要知道递归的参数,终止条件,这里递归参数包括辅助矩阵visit(用于保存已经访问了的路径),当前格子的i,j索引和数位之和限制k,数位之和计算比较简单,不再赘述。递归终止条件为超出索引界限、visit[][]为true、数位之和大于k,这些条件之间用或连接,然后标记访问数组为true,然后返回值为1+向左和向右搜索的结果,其中1代表当前起始格子,。至于为什么返回值为1+向左向右搜索的结果,可以结合leetcode题解的图来形象理解。
class Solution {
public int movingCount(int m, int n, int k) {
boolean[][] visited = new boolean[m][n];
return dfs(visited, m, n, k, 0, 0);
}
//递归函数首先要确定其定义代表什么,也就是返回值。
//然后根据返回值含义先递归遍历。
//再确定合适的边界条件,即终止递归条件。
//其中还需要注意细节问题,也就是是否需要标记已遍历的路径或者恢复现场。
//此题dfs返回的就是从当前坐标开始,一共有多少个格子可达,其总数=1+右边下+下边还有几个格子可达。
//因此,1+dfs(下边)+dfs(右边)即为格子数。
private int dfs(boolean[][] visited, int m, int n, int k, int i, int j) {
if(i >= m || j >= n || visited[i][j] || bitSum(i) + bitSum(j) > k) return 0;
visited[i][j] = true;
return 1 + dfs(visited, m, n, k, i + 1, j) + dfs(visited, m, n, k, i, j + 1) ;
}
private int bitSum(int n) {
int sum = 0;
while(n > 0) {
sum += n % 10;
n /= 10;
}
return sum;
}
}
此题主要涉及到一些基本的数学推导,也是分为动态规划和贪心算法两种方式。最容易想到的就是贪心算法,虽然我也是看了题解才明白的。。。。。害,真的是感叹每日一菜。
贪心算法中的解题要点就是每次剪绳子的长度最好为3,如果剩下长度是4的话,则剪为2*2=4。然后程序里面首先用if定义绳子长度小于5的情况,然后一个循环,实现maxProduct的计算,每次n为n-3,直到n<5,则跳出循环,再将maxProduct*n即为所求。代码如下:
class Solution {
public int cuttingRope(int n) {
int maxProduct = 1;
if(n==2){
return 1;
}
if(n==3) return 2;
if(n==4) return 4;
while(n>=5){
maxProduct *= 3;
n -= 3;
}
return maxProduct*n;
}
}
此题主要涉及到防止大数越界的问题,需要进行求模,整体思路无太大变化。
需要注意的就是注意到类型强制转换,不然会报错。
class Solution {
public int cuttingRope(int n) {
long maxProduct = 1L;
if(n==2){
return 1;
}
if(n==3) return 2;
if(n==4) return 4;
int p = (int)1e9+7;
while(n>=5){
maxProduct = (maxProduct*3)%p;
n -= 3;
}
return (int)(maxProduct*n%p);
}
}
此题主要涉及到位运算,通过与1进行位运算,判断该位是否为1。由于采用右移的话,当遇到最高位为1的时候,会出现死循环,FFFFFFF的情况,因此,这里通过将1与原数字的从低到高位进行与运算,判断是否为0,再进行计数即可得到1的个数。
public class Solution {
// you need to treat n as an unsigned value
public int hammingWeight(int n) {
int m =1;
int count=0;
for(int i=0;i<32;i++){
if((m & n) != 0){
count++;
}
m= m <<1;
}
return count;
}
}
此题最逆天的解法就是知道n&(n-1)即代表将n最右边的1化为0,这个规律很有用,很多二进制的题都有涉及。
前段时间论文返修意见回来了,然后就一直忙着改论文,都没怎么刷题了,感觉又落下了好多诶。今天把论文改完了,然后又开始接着刷题了。言归正传,这道题最直接的方法就是用循环的方式计算数值的整数次方,如下所示:
class Solution {
public double myPow(double x, int n) {
double result =1;
if (x==0){
if(n!=0)
return result =0;
else {
result = -1;}
}
if(n<0){
n=-n;
for(int i=0;i<n;i++){
result *= x;
}
result= 1/result;
}
else if(n==0){
result=1;}
else{
for(int i=0;i<n;i++){
result *= x;
}
}
return result;
}
}
这样做的结果就是计算时间过长,无法通过测试。还是只有看题解,题解中提到两种解法,一种涉及到数学推导,直接略过。故直接看第二种没那么反人类的解法。但还是需要用到递归的手段。代码如下:
class Solution {
public double myPow(double x, int n) {
long m = Math.abs((long)n);
double num =calculate(x,m);
if(n < 0) return 1 / num;
return num;
}
public double calculate(double x,long n){
if(n == 0) return 1;
if(n == 1) return x;
double a = calculate(x,n>>1);
if(n % 2 == 0){
return a * a;
}
return a * a * x;
}
}```

## Q17 打印从1到最大的n位数
此题因为看力扣归为简单题,因此一上来就想到定义一个数组,然后for循环添加。代码如下:
```java
class Solution {
public int[] printNumbers(int n) {
int arrLen = 0;
arrLen =(int)Math.pow(10,n)-1;
int []res =new int[arrLen];
for(int i=0;i<arrLen;i++){
res[i]=i+1;
}
return res;
}
}
虽然顺利通过了测试,但是剑指offer上面主要考察大数问题,要将其转化为字符串的形式。当然这也是不看题解所想不出来的。阿西吧~~~~~
这道题乍一看,以为很简单,直接定义一个一维数组,长度为10^n-1,然后循环打印出来即可。可事实上没那么简答,哎。。。。
要考虑到大数问题,也就是当n很大时,会超出 int的表示范围。因此,需要用字符串来辅助。这里面又涉及到递归的知识,需要先固定高位,再固定低位。通过循环实现,这里递归终止的条件为最低位被固定。目前考虑到的问题尚能理解,结果一看题解,还要考虑删除多余的0以及转为int型。。。。。实在是脑力不够了,,,,卒。先放在这儿吧,以后再回过头来看。
class Solution {
StringBuilder res;
int nine = 0, count = 0, start, n;
char[] num, loop = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
public String printNumbers(int n) {
this.n = n;
res = new StringBuilder();
num = new char[n];
start = n - 1;
dfs(0);
res.deleteCharAt(res.length() - 1);
return res.toString();
}
void dfs(int x) {
if(x == n) {
String s = String.valueOf(num