【数据结构与算法】-【剑指offer题目及Java代码】

剑指offer题目及Java代码(不断学习更新,欢迎批评指正)

1.赋值运算函数(未解)

2.单例设计模式(未解)

3.二维数组中查找目标值【数组】【查找】

题目描述:
在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列 都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一 个整数,判断数组中是否含有该整数。
思路:
根据矩阵性质从右上角或左下角开始找,根据比较结果逐行、列排除,或者用二分法查找。
代码实现:
解法1:根据矩阵性质

public class Solution {
    public boolean Find(int target, int [][] array) {
        if((array == null || array.length == 0) || (array[0].length == 0)){
            return false;
        }
         
        int row = 0;
        int col = array[0].length - 1;
         
        while(row < array.length && col >= 0){
            if(array[row][col] == target){
                return true;
            }
            if(array[row][col] > target){
                col--;
            }else{
                row++;
            }
        }
         
        return false;
    }
}

解法2:逐行二分法

public class Solution {
    public boolean Find(int [][] array,int target) {
         
        for(int i=0;i<array.length;i++){
        
            int left = 0;
            int right = array[i].length-1;
            
            while(left <= right){
            
                int mid = (left + right) / 2;
                
                if(target > array[i][mid]){
                    left = mid + 1;
                }
                else if(target<array[i][mid]){
                    right = mid - 1;
                }
                else{
                    return true;
               }
            }
        }
        return false;
 
    }
}

4.替换字符串中的空格【字符串】

题目描述:
将一个字符串中的空格替换成“%20”。例如:当字符串为 We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
思路:
从后往前复制,数组长度会增加,或使用 StringBuilder、StringBuffer 类。
代码实现:

public class Solution {
    public String replaceSpace(StringBuffer str) {
    	if(str == null){
            return null;
        }
        
        StringBuilder sb = new StringBuilder();
        
        for(int i = 0;i < str.length();i++){
            if(String.valueOf(str.charAt(i)).equals(" ")){
                sb.append("%20");
            }else{
                sb.append(str.charAt(i));
            }
        }
        return String.valueOf(sb);
    }
}

5. 从尾到头打印链表【链表】【栈】

题目描述:
输入一个链表,从尾到头打印链表每个节点的值。
思路:
借助栈实现,或使用递归的方法。
代码实现:
解法1:借用栈

/**
*    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) {
        ArrayList<Integer> list = new ArrayList<Integer>(); 
        Stack<ListNode> stack = new Stack<ListNode>();      
        
        while (listNode != null) { 
            stack.push(listNode); 
            listNode = listNode.next;
        }
        //此处使用stack的 isEmpty() 方法,而不是stack  != null
        while (!stack.isEmpty()) {
            list.add(stack.pop().val);
        }
        return list;
    }
}

解法2:递归

/**
*    public class ListNode {
*        int val;
*        ListNode next = null;
*
*        ListNode(int val) {
*            this.val = val;
*        }
*    }
*
*/

import java.util.ArrayList;

public class Solution {
    
    ArrayList<Integer> list = new ArrayList<>(); 
    
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        
        if(listNode != null){
            printListFromTailToHead(listNode.next);
            list.add(listNode.val);
        }

        return list;
    }
}

6. 由前序和中序遍历重建二叉树【树】【数组】【dfs】

题目描述:
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假 设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列 {1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,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.Arrays;

public class Solution {
    public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
        if (pre.length == 0 || in.length == 0) {
            return null;
        }
        if (pre.length != in.length) {
            return null;
        }
        
        TreeNode root = new TreeNode(pre[0]);
        
        //在中序中找根
        for(int i = 0;i < in.length;i++){
           if(in[i] == pre[0]){
               //public static int[] copyOfRange(int[] original,int from,int to) 将指定数组的指定范围复制到一个新数组。
               //注意 copyOfRange 函数,左闭右开
               //左子树
               root.left = reConstructBinaryTree(Arrays.copyOfRange(pre,1,i+1) , Arrays.copyOfRange(in,0,i));
               //右子树
               root.right = reConstructBinaryTree(Arrays.copyOfRange(pre,i+1,pre.length) , Arrays.copyOfRange(in,i+1,in.length));
               break;
           } 
        }
        
        return root;
        
    }
}

7.用两个栈实现队列【栈】

题目描述:
用两个栈来实现一个队列,完成队列的 Push 和 Pop 操作。 队列中的元素为 int 类型。
思路:
一个栈压入元素,而另一个栈作为缓冲,将栈 1 的元素出栈后压入栈 2 中,再弹出栈2栈顶元素弹出。
代码实现:

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();
    }
}

8. 求旋转数组的最小数字 【二分】

题目描述:
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。 例如 数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为 1。 NOTE:给出的所 有元素都大于 0,若数组大小为 0,请返回0。假设数组中不存在重复元素。
思路:
利用二分法,找到数组的中间元素 mid。如果中间元素 > 数组第一个元素, 在 mid 右边搜索变化点。如果中间元素 < 数组第一个元素,我们需要在 mid 左边 搜索变化点。当找到变化点时停止搜索,满足 nums[mid] > nums[mid + 1] (mid+1 是最小值)或 nums[mid - 1] > nums[mid]( mid 是最小值)即可。
代码实现:

import java.util.ArrayList;

public class Solution {
    public int minNumberInRotateArray(int [] array) {
        if(array == null || array.length == 0){
            return 0;
        }
        if(array.length == 1){
            return array[0];
        }
        
        int left = 0;
        int right = array.length - 1;
        
        while(left <= right){
            int mid = (left + right)/2;
            if(array[mid] > array[mid + 1]){
                return array[mid + 1];
            }
            if(array[mid - 1] > array[mid]){
                return array[mid];
            }
            if(array[mid] > array[0]){
                left = mid + 1;
            }else{
                right = mid - 1;
            }
        }
        
        return 0;
    }
}

9. 斐波那契数列的应用【递归】

9.1 输出斐波那契数列的第 n 项

题目描述:
现在要求输入一个整数 n,请你输出斐波那契数列的第 n 项。n<=39
思路:
递归。考虑到递归的效率低,推荐使用循环方式。
代码实现:
解法1:递归

public class Solution {
    public int Fibonacci(int n) {
        if(n == 0 || n == 1){
            return n;
        }
        return Fibonacci(n - 2) + Fibonacci(n - 1);
    }
}

解法2:循环

public class Solution {
    public int Fibonacci(int n) {
        int preOne = 1;
        int preTwo = 1;
        int result = 0;
        
        if(n == 0){
            return 0;
        }
        if(n == 1 || n == 2){
            return 1;
        }
        for(int i = 2;i < n;i++){
            result = preOne + preTwo;
            preOne = preTwo;
            preTwo = result;
        }
        
        return result;
    }
}

利用数列关系简化变量

public class Solution {
    public int Fibonacci(int n) {
        int preOne = 1;
        int preTwo = 1;
            
        if(n == 0){
            return 0;
        }
        if(n == 1 || n == 2){
            return 1;
        }
        for(int i = 2;i < n;i++){
            preTwo = preOne + preTwo;
            preOne = preTwo - preOne;
        }
        
        return preTwo;
    }
}

9.2. 青蛙跳台阶(1 或 2 级)

题目描述:
一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级。求该青蛙跳上一 个 n 级的台阶总共有多少种跳法。
思路:
斐波那契数列思想 :
0级台阶:0种跳法
1级台阶:1种跳法
2级台阶:2种跳法
3级台阶:3种跳法
4级台阶:5种跳法
5级台阶:8种跳法
… …
当n<3时,n种跳法。
当n>=3时,符合斐波那契数列排列思想。
递归。考虑到递归的效率低,推荐使用循环方式。
代码实现:
解法1:递归

public class Solution {
    public int JumpFloor(int target) {
        if(target < 3){
            return target;
        }
        return JumpFloor(target - 1) + JumpFloor(target - 2);
    }
}

解法2:循环

public class Solution {
    public int JumpFloor(int target) {
        if(target < 3){
            return target;
        }
        int result = 0;
        int preOne = 1;
        int preTwo = 2;
        
        for(int i = 2 ;i < target; i++){
            result = preOne + preTwo; 
            preOne = preTwo; 
            preTwo = result;
        }
        return result;
    }
}

利用数列关系简化变量

public class Solution {
    public int JumpFloor(int target) {
        if(target < 3){
            return target;
        }
        int preOne = 1;
        int preTwo = 2;

        for (int i = 2; i < target; i++) {
            preTwo = preTwo + preOne;
            preOne = preTwo - preOne;
        }
        return preTwo;
    }
}

9.3 小矩形无重叠覆盖大矩形

题目描述:
我们可以用 2×1 的小矩形横着或者竖着去覆盖更大的矩形。请问用 n 个 2×1 的小矩形无重叠地覆盖一个 2×n 的大矩形,总共有多少种方法?
思路:
斐波那契数列思想 :
在这里插入图片描述
如图当n = 3时,为三种方法。
n = 0时:0种方法
n = 1时:1种方法
n = 2时:2种方法
n = 3时:3种方法
n = 4时:5种方法
n = 5时:8种方法
… …
斐波那契数列思想
代码实现:
同9.2台阶。

9.4. 青蛙跳台阶(n 级)

题目描述:
一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级……它也可以跳上 n 级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
思路:
设 n 级台阶有 n 种跳法,根据最后一次跳台阶的数目可以分解为最后一次一级,则前面需要跳 n-1 级,有 f(n-1) 种跳法;最后一次跳两级,则前面需要跳 n-2 级,有 f(n-2) 种跳法。以此类推 易知,

在这里插入图片描述
两式相减得
在这里插入图片描述

在这里插入图片描述
代码实现:

public class Solution {
    public int JumpFloorII(int target) {
    	//需要注意左移和pow的区别
        return target <= 0 ? 0 : 1 << (target - 1);
    }
}

采用math类

public class Solution {
	    public int JumpFloorII(int target) {
	        return (int) Math.pow(2,target-1);
	    }
	}

10. 二进制中 1 的个数【进制转换】

题目描述:
输入一个整数,输出该数二进制表示中 1 的个数。其中负数用补码表示。
思路:
a&(a-1)的结果会将 a 最右边的 1 变为 0,直到 a = 0 。
如果一个整数不为0,那么这个整数至少有一位是1。如果我们把这个整数减1,那么原来处在整数最右边的1就会变为0,原来在1后面的所有的0都会变成1(如果最右边的1后面还有0的话)。其余所有位将不会受到影响。
举个例子:一个二进制数1100,从右边数起第三位是处于最右边的一个1。减去1后,第三位变成0,它后面的两位0变成了1,而前面的1保持不变,因此得到的结果是1011.我们发现减1的结果是把最右边的一个1开始的所有位都取反了。这个时候如果我们再把原来的整数和减去1之后的结果做与运算,从原来整数最右边一个1那一位开始所有位都会变成0。如1100&1011=1000.也就是说,把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0.那么一个整数的二进制有多少个1,就可以进行多少次这样的操作。
代码实现:

public class Solution {
    public int NumberOf1(int n) {
        int count = 0;
        while(n != 0){
            count++;
            n = n & (n-1); 
        }
        return count;
    }
}

11. 数值的整数次方【快速幂】【浮点数比较】

题目描述:
给定一个 double 类型的浮点数 base 和 int 类型的整数 exponent。保证base和exponent不同时为0,求 base 的 exponent 次方。不得使用库函数,不需要考虑大数问题 。
思路:
不能用 == 比较两个浮点数是否相等,因为有误差。使用了有限空间来存储无限小数,必然只能存储一个近似值,而不同的计算过程影响了近似值的精度。显然,如果使用 “==” 运算符对两个有着不同精度的近似值逐位进行比较,有可能会得到false的判断结果。同时需要考虑输入值的多种情况。
代码实现:
解法1:暴力法

public class Solution {
    public double Power(double base, int exponent) {
        
        double result = 1.0;
        
       //特殊情况的考虑
        if(equal(base,0)){
            return 0;
        }
        if(exponent == 0){
            return 1;
        }
        
        //求x^(-2)可以转换成(1/x)^2,以下为预处理代码
        if(exponent < 0){
            base = 1/base;
            exponent = -exponent;
        }
        for(int i = 0;i < exponent;i++){
            result *= base;
        }
        return result;
  	}
    
    //比较两个浮点数是否相等
    public boolean equal(double a,double b){
        if(a - b < 1e-6 && a - b > -1e-6){
            return true;
        }
        return false;
    }
}

解法2:递归法(快速幂)
假设我们求x ^ 8 ,如果我们知道x ^ 4 ,那么图片x ^ 8=(x ^ 4)2 ,所以
在这里插入图片描述
如果n是偶数,那么上述没问题。如果n是奇数,
在这里插入图片描述

public class Solution {
    public double Power(double base, int exponent) {

        if(equal(base,0)){
            return 0;
        }
        if(exponent == 0){
            return 1;
        }
        double result = powerStep(base,exponent >> 1);
       
        //此处奇数判定不能使用exponent%2 == 1,否则当exp = 3/2 时结果为3/2无法通过。
        //而3/2的二进制为1.1与1相与后仍为1
        
        if((exponent & 1) == 1){
            return result * result * base;
        }else{                    
            return result * result;
        }
  }
    
    public double powerStep(double base, int exponent){
        if(exponent < 0){
            base = 1/base;
            exponent = -exponent;
        }
        return Power(base,exponent);
    }
    
    public boolean equal(double a,double b){
        if(a - b < 1e-6 && a - b > -1e-6){
            return true;
        }
        return false;
    }
}

解法3:非递归的快速幂
假设求x ^ 6 ,已知6可以表示成二进制110,可以表示成
在这里插入图片描述
所以x ^ 6可以表示成

在这里插入图片描述
所以,对于二进制数,遇到位数是1的就乘到答案中。

public class Solution {
    public double Power(double base, int exponent) {

        if(equal(base,0)){
            return 0;
        }
        if(exponent == 0){
            return 1;
        }
        if(exponent < 0){
            base = 1/base;
            exponent = -exponent;
        }
        
        double x = base;
        double result = 1.0;
        while(exponent != 0){
        	//遇到 二进制中从低位到高位的第一个1
            if((exponent & 1) != 0){
                //乘入结果
                result *= x;
            }
            //二进制进位 2^1  2^2  2^3 ...
            x *= x;
            //右移一位 因为判定是exponent & 0000 0001
            exponent >>= 1;                      
        }
        return result;	
  }
        
    public boolean equal(double a,double b){
        if(a - b < 1e-6 && a - b > -1e-6){
            return true;
        }
        return false;
    }
}

12. 求 1 到最大的 n 位数

题目描述:
输入数字 n,按顺序打印从 1 到最大的 n 位数十进制数,比如:输入 3,打印出 1 到 999.
思路:
考虑大数问题,使用字符串或数组表示。
代码实现:

public void printToMaxOfNDigits(int n) { 
	int[] array=new int[n]; 
	if(n <= 0)
		 return; 
	printArray(array, 0); 
	}
private void printArray(int[] array,int n) { 
	for(int i = 0; i < 10; i++) { 
		if(n != array.length) { 
			array[n] = i; 
			printArray(array, n+1); 
		} else { 
			boolean isFirstNo0 = false; 
			for(int j = 0; j < array.length; j++) { 
				if(array[j] != 0) { 
					System.out.print(array[j]); 
					if(!isFirstNo0) 
						isFirstNo0 = true; 
				} else { 
					if(isFirstNo0) 
						System.out.print(array[j]); 
				} 
			}
			System.out.println(); 
			return ; 
		} 
	} 
}

13. O(1)时间删除链表节点 【链表】

题目描述:
给定单向链表的头指针和一个节点指针,在 O(1)时间复杂度内删除该节点。
思路:
将要删除节点的下一个节点的值赋给要删除的节点,然后指向下下一个节点
代码实现:

public void deleteNode(ListNode head, ListNode deListNode) { 
	if (deListNode == null || head == null) 
		return; 

	if (head == deListNode) { 
		head = null; 
	} else { 
		// 若删除节点是末尾节点,链表往向后移一位 
		if (deListNode.nextNode == null) { 
			ListNode pointListNode = head; 
			while (pointListNode.nextNode.nextNode != null) { 
				pointListNode = pointListNode.nextNode; 
			}
			pointListNode.nextNode = null; 
		} else { 
			//要删除节点的下一个节点的值赋给要删除的节点
			deListNode.data = deListNode.nextNode.data; 
			//指向下下一个节点
			deListNode.nextNode = deListNode.nextNode.nextNode; 
		} 
	} 
}

14. 将数组中的奇数放在偶数前【数组】 【双指针】

题目描述:
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变 。
思路:
O(n) 遍历一次数组,遇到奇数直接放入新开的数组中,再遍历一次数组,遇到偶数就继续放入新开的数组。最后再进行一次copy.
O(n^2) 类似与插入排序
在这里插入图片描述

代码实现:

public class Solution {
    public void reOrderArray(int [] array) {
        if(array == null || array.length == 0){
            return;
        }
        
        int cursor = 0;
        for(int i = 0;i < array.length;i++){
            int item = array[i];
            //如果是奇数
            if((item&1) == 1){
            	//不为第一个元素
                if(i != 0){
                   //奇数数组指针cursor至偶数数组指针i后移一位
                    for(int j = i;j>cursor;j--){
                        array[j] = array[j-1];
                    }
                    //插入元素(末尾换到第一位)
                    array[cursor] = item;
                }
                //奇数数组指针cursor后移
                cursor++;
            }
        }
        
    }
}

15. 求链表中倒数第 K 个节点 【链表】【双指针】

题目描述:
输入一个链表,输出该链表中倒数第 k 个结点。
思路:
定义一快一慢两个指针,快指针走 K 步,然后慢指针开始同步走,快指针到尾时,慢指针就找到了倒数第 K 个节点。
代码实现:

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode FindKthToTail(ListNode head,int k) {
        if(head == null || k == 0){
            return null;
        }
        
        ListNode fast = head;
        ListNode slow = head;
        
        for(int i= 0;i<k;i++){
        	//注意k的值存在超过链表长度的情况
            if(fast == null){
                return null;
            }
            fast = fast.next;
        }
        
        while(fast != null){
            fast = fast.next;
            slow = slow.next;
        }
        
        return slow;
    }
}

16. 输出反转后的链表 【链表】

题目描述:
输入一个链表,反转链表后,输出新链表的表头。
思路:
定义两个指针,反向输出 。
初始化:2个指针pre cur
1)pre指针指向已经反转好的链表的最后一个节点,最开始没有反转,所以指向nullptr
2)cur指针指向待反转链表的第一个节点,最开始第一个节点待反转,所以指向head
临时tmp指针指向待反转链表的第二个节点,目的是保存链表,因为cur改变指向后,后面的链表则失效了,所以需要保存
接下来,循环执行以下三个操作
1)tmp = cur->tmp, 保存后续链表作用
2)cur->tmp = pre 未反转链表的第一个节点的下个指针指向已反转链表的最后一个节点
3)pre = cur, cur = tmp; 指针后移,操作下一个未反转链表的第一个节点
循环条件是cur != null,循环结束后,cur为nullptr,所以返回pre,即为反转后的头结点.
代码实现:

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode ReverseList(ListNode head) {
        //新链表
        ListNode pre = null;
        //原链表
        ListNode cur = head;
        
        while(cur != null){
        	//防止后续链表失效
            ListNode temp = cur.next;
            //当前节点指向新链表头部
            cur.next = pre;
            pre = cur;
            //指向原链表新头部
            cur = temp;
        }
        return pre;
    }
}

17. 合并两个有序链表 【链表】【递归】

题目描述:
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
思路:
递归与非递归求解,小数放在前面。
初始化:定义cur指向新链表的头结点
1.如果l1指向的结点值小于等于l2指向的结点值,则将l1指向的结点值链接到cur的next指针,然后l1指向下一个结点值。
2.否则,让l2指向下一个结点值。
3.循环步骤1,2,直到l1或者l2为null。
4.将l1或者l2剩下的部分链接到cur的后面。
代码实现:
解法1:迭代

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode Merge(ListNode list1,ListNode list2) {
        ListNode listHead = new ListNode(-1);
        ListNode cursor = listHead;
        
        while(list1 != null && list2 != null){
            if(list1.val < list2.val){
            	//合成链表添加list1中小元素
                cursor.next = list1;
                //指针后移一位
                list1 = list1.next;
            }else{
           	 	//合成链表添加list1中小元素
                cursor.next = list2;
                //指针后移一位
                list2 = list2.next;
            }
            cursor = cursor.next;
        }
        //非空后续链表接入
        cursor.next = list1 == null ? list2 : list1;
        //
        return listHead.next;
    }
}

解法2:递归

public ListNode mergeTwoLists(ListNode list1, ListNode list2) { 
	if (list1 == null) { 
		return list2; 
	}
	if (list2 == null) { 
		return list1; 
	}

	if (list1.val < list2.val) { 
		list1.next = mergeTwoLists(list1.next, list2); 
		return list1; 
	} else { 
		list2.next = mergeTwoLists(list1, list2.next); 
		return list2; 
	} 
}

18. 判断二叉树 A 中是否包含子树 B【二叉树】【递归】

题目描述:
输入两棵二叉树 A,B,判断 B 是不是 A 的子结构。(ps:我们约定空树不是任意一个树的子结构)
思路:
若根节点相等,利用递归比较他们的子树是否相等,若根节点不相等,则利用递归分别在左右子树中查找。
代码实现:

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
public class Solution {
	//前序遍历树
    public boolean HasSubtree(TreeNode root1,TreeNode root2) {
        if(root1 == null || root2 == null){
            return false;
        }
        //如果找到与子树相同根的值,判断
        if(root1.val == root2.val){
            if(judge(root1,root2)){
                return true;                
            }
        }
        
        //遍历左树,右树
        return HasSubtree(root1.left,root2) || HasSubtree(root1.right,root2);
    }
    
    //判断是否是子结构
    //子树的意思是只要包含了一个结点,就得包含这个结点下的所有节点. 
	//子结构的意思是包含了一个结点,可以只取左子树或者右子树,或者都不取。
    public boolean judge(TreeNode tree,TreeNode sub){
        //子结构已经循环完毕,代表全部匹配
        if(sub == null){
            return true;
        }
        //大树已经循环完毕,并未成功匹配
        if(tree == null){
            return false;
        }
        //相等后判断左右子树
        if(tree.val == sub.val){
            return judge(tree.left,sub.left) && judge(tree.right,sub.right);
        }
        return false;
    }
}

19. 二叉树的镜像【二叉树】【递归】

题目描述:
操作给定的二叉树,将其变换为源二叉树的镜像。
思路:
使用递归或非递归方式交换每个节点的左右子树位置。
代码实现:
解法1:递归

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/


public class Solution {
    public void Mirror(TreeNode root) {
        if(root == null){
            return ;
        }
        
        TreeNode temp = root.left;
        root.left = root.right;
        root.right = temp;
        Mirror(root.left);
        Mirror(root.right);
    }
}

解法2:非递归

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/

import java.util.Queue;
import java.util.LinkedList;

public class Solution {
    public void Mirror(TreeNode root) {
        if(root == null){
            return ;
        }
        
        TreeNode curr;
        TreeNode temp;
        
        Queue<TreeNode> nodes = new LinkedList<TreeNode>();
        nodes.offer(root);
        
        //左右互换
        while(!nodes.isEmpty()){
            curr = nodes.poll();
            temp = curr.left;
            curr.left = curr.right;
            curr.right = temp;
            if(curr.left != null){
                nodes.offer(curr.left);
            }
            if(curr.right != null){
                nodes.offer(curr.right);
            }
        }
    }
}

20. 顺时针打印矩阵 【数组】

题目描述:
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下 4X4 矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次 打印出数字 1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.
思路:
按层模拟:终止行号大于起始行号,终止列号大于起始列号,不断地收缩矩阵的边界。注意避免奇数维矩阵的重复打印。
代码实现:

import java.util.ArrayList;

public class Solution {
    public ArrayList<Integer> printMatrix(int [][] matrix) {
        ArrayList<Integer> result = new ArrayList<>();
        if(matrix == null || matrix.length == 0){
            return result;
        }
        
        int r1 = 0;
        int r2 = matrix.length - 1;
        int c1 = 0;
        int c2 = matrix[0].length - 1;
        
        while(r1 <= r2 && c1 <= c2){
            //从左上向右上
            for(int c = c1;c <= c2;c++){
                result.add(matrix[r1][c]);
            }
            //从右上向右下
            for(int r = r1 + 1;r <= r2;r++){
                result.add(matrix[r][c2]);
            }
            //避免奇数维矩阵中心重复打印
            if(r1 < r2 && c1 < c2){
                //从右下向左下
                for(int c = c2 - 1;c >= c1;c--){
                    result.add(matrix[r2][c]);
                }
                //从左下向左上
                for(int r = r2 - 1;r > r1;r--){
                    result.add(matrix[r][c1]);
                }
            }
            r1++;
            r2--;
            c1++;
            c2--;            
        }
        
        return result;
    }
}

21. 包含 min 函数的栈【栈】

题目描述:
定义栈的数据结构,请在该类型中实现一个能够得到栈最小元素的 min 函数。
思路:
定义两个栈,一个存放入的值。另一个存最小值。
在这里插入图片描述

代码实现:

import java.util.Stack;

public class Solution {
    
    //Java中的int是基本数据类型,没有继承自Object类,要存放整型数据必须使用整型的封装类
    Stack<Integer> stackNor = new Stack<Integer>();
    Stack<Integer> stackMin = new Stack<Integer>();
    
    public void push(int node) {
        stackNor.push(node);
        if(stackMin.isEmpty()){
            stackMin.push(node);
        }else{
            if(stackMin.peek() >= node){
                stackMin.push(node);
            }
        }       
    }
    
    public void pop() {
        if(stackMin.peek() == stackNor.peek()){
            stackMin.pop();
        }
        stackNor.pop();
    }
    
    public int top() {
        return stackNor.peek();
    }
    
    public int min() {
        return stackMin.peek();
    }
}

22. 判断一个栈是否是另一个栈的弹出序列【栈】

题目描述:
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列 1,2,3,4,5 是某栈的压入顺序,序列4,5,3,2,1 是该压栈序列对应的一个弹出序列,但 4,3,5,1,2 就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等 的)
思路:
用栈来压入弹出元素,相等则出栈。 新建一个栈,将数组A压入栈中,当栈顶元素等于数组B时,就将其出栈,当循环结束时,判断栈是否为空,若为空则返回true.。
在这里插入图片描述
因为弹出之前的值都会先入栈,所以这里用个栈来辅助。
1.初始化:用指针i指向pushV的第一个位置, 指针j指向popV的第一个位置
2.如果pushV[i] != popV[j], 那么应该将pushV[i]放入栈中, ++i
3.否则,pushV[i] == popV[j], 说明这个元素是放入栈中立马弹出,所以,++i, ++j,然后应该检查popV[j]与栈顶元素是否相等,如果相等,++j, 并且弹出栈顶元素
4.重复2,3, 如果i==pushV.size(), 说明入栈序列访问完,此时检查栈是否为空,如果为空,说明匹配,否则不匹配。
代码实现:

import java.util.ArrayList;
import java.util.Stack;

public class Solution {
    public boolean IsPopOrder(int [] pushA,int [] popA) {
        if(pushA == null || popA == null || pushA.length != popA.length){
            return false;
        }
        
        Stack<Integer> stack = new Stack<Integer>();
        //弹出序列数组指针
        int index = 0;
        for(int i = 0;i < pushA.length;i++){
            stack.push(pushA[i]);
            //与弹出序列相对应则弹出
            while(!stack.isEmpty() && stack.peek() == popA[index]){
                stack.pop();
                index++;
            }
        }
        //剩余则不可能是该压栈序列的弹出序列
        return stack.isEmpty();
    }
}

23.层序遍历二叉树 【树】【队列】

题目描述:
从上往下打印出二叉树的每个节点,同层节点从左至右打印。
思路:
利用队列(链表)辅助实现。先进先出,按顺序放入队列即可。
代码实现:

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/

import java.util.Queue;
import java.util.ArrayList;
import java.util.LinkedList;

public class Solution {
    public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
        
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        ArrayList<Integer> result= new ArrayList<Integer>();
        
        if(root == null){
           return result; 
        }
        
        queue.offer(root);
        
        while(!queue.isEmpty()){
            TreeNode temp = queue.poll();
            result.add(temp.val);
            if(temp.left != null){
                queue.offer(temp.left);
            }
            if(temp.right != null){
                queue.offer(temp.right);
            }    
        }
        return result;
    }
}

24. 后序遍历二叉搜索树 【树】【栈】【递归】

题目描述:
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结 果。如果是则输出 Yes,否则输出 No。假设输入的数组的任意两个数字都互不相 同。
思路:
先找到右子树的开始位置,然后分别进行左右子树递归处理。
代码实现:

public class Solution {
    public boolean VerifySquenceOfBST(int [] sequence) {
        if(sequence.length == 0 || sequence == null){
            return false;
        }
        return verify(sequence,0,sequence.length - 1);
    }
    
    public boolean verify(int [] sequence,int start,int end){
        //递归终止条件
        if(start > end){
            return true;
        }
        int i;
        //查找左右子树分界点
        for(i = start;i < end;i++){
            if(sequence[i] > sequence[end]){
                break;
            }
        }
        //查找右子树是否存在小于根节点的数
        for(int j = i;j < end;j++){
            if(sequence[j] < sequence[end]){
                return false;
            }
        }
        return verify(sequence,start,i - 1) && verify(sequence,i,end - 1);     
    }
}

25. 二叉树中和为某值的路径 【树】【递归】

题目描述:
输入一颗二叉树和一个整数,打印出二叉树中结点值的和为输入整数 的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一 条路径。
思路:
先保存根节点,然后分别递归在左右子树中找目标值,若找到即到达叶子节点,打印路径中的值
代码实现:

import java.util.ArrayList;
/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
public class Solution {
    public ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>();
    public ArrayList<Integer> list = new ArrayList<Integer>();
    
    public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
        if(root == null){
            return result;
        }           
        list.add(root.val);
        target -= root.val;
        if(target == 0 && root.left == null && root.right == null){
            result.add(new ArrayList<Integer>(list));
        }
        FindPath(root.left,target);
        FindPath(root.right,target);
        //回溯到父节点
        list.remove(list.size() - 1);
        return result;
    }
}

26. 复杂链表的复制【链表】

题目描述:
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的 head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
思路:
先复制链表的 next 节点,将复制后的节点接在原节点后,然后复制其它的节点,最后取偶数位置的节点(复制后的节点)。
代码实现:
解法1:直接复制,先复制next节点,再复制其他节点

后来想了下,该方法存在问题,即复制任意节点时,原链表指向的后续节点还没复制,则会出现多余重复节点,所以该方法虽然可通过牛客网,仍然不能使用。

/*
public class RandomListNode {
    int label;
    RandomListNode next = null;
    RandomListNode random = null;

    RandomListNode(int label) {
        this.label = label;
    }
}
*/
public class Solution {
    public RandomListNode Clone(RandomListNode pHead)
    {
        if(pHead == null) {
            return null; 
        }
        RandomListNode head = new RandomListNode(pHead.label) ; 
        RandomListNode temp = head ; 
        while(pHead.next != null) { 
            temp.next = new RandomListNode(pHead.next.label) ; 
            if(pHead.random != null) { 
                temp.random = new RandomListNode(pHead.random.label) ; 
            }
            pHead = pHead.next ; 
            temp = temp.next ; 
        }
        return head ;
    }
}

解法2:先复制链表的 next 节点,将复制后的节点接在原节点后,然后复制其它的节点,最后取偶数位置的节点
在这里插入图片描述

/*
public class RandomListNode {
    int label;
    RandomListNode next = null;
    RandomListNode random = null;

    RandomListNode(int label) {
        this.label = label;
    }
}
*/
public class Solution {
    public RandomListNode Clone(RandomListNode pHead)
    {
        if(pHead == null) {
            return null; 
        }
        
        RandomListNode currentNode = pHead;
        
        //复制节点,并接到对应原节点后
        while(currentNode != null){
            RandomListNode cloneNode = new RandomListNode(currentNode.label);
            //存储原节点后继
            RandomListNode tempNode = currentNode.next;
            //将新节点接至原节点后面
            currentNode.next = cloneNode;
            //新节点后继接原链表
            cloneNode.next = tempNode;
            //指针后移
            currentNode = tempNode;                      
        }

        //复制随机指针
        currentNode = pHead;
        while(currentNode != null){
            currentNode.next.random = currentNode.random == null? null:currentNode.random.next;
            //后移两次
            currentNode = currentNode.next.next;
        }
        
        //拆分链表
        currentNode = pHead;
        RandomListNode pCloneHead = currentNode.next;
        while(currentNode != null){	
            RandomListNode cloneNode = currentNode.next;
            //去除克隆节点
            currentNode.next = cloneNode.next;           
            cloneNode.next = cloneNode.next == null? null: cloneNode.next.next;
            currentNode = currentNode.next;          
        }
                       
        return pCloneHead;
    }	    
}

27. 二叉搜索树转换为双向链表(未解)

题目描述:
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。 要求不能创建任何新的结点,只能调整树中结点指针的指向。
思路:
定义一个链表的尾节点,递归处理左右子树,最后返回链表的头节点
代码实现:

28. 打印字符串中所有字符的排列【字符串】【递归】【动态规划】

题目描述:
输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串 abc,则打印出由字符 a,b,c 所能排列出来的所有字符串 abc,acb,bac,bca,cab 和 cba。
思路:
将当前位置的字符和前一个字符位置交换,递归。

假设输入为a、b、c,那么其实排序的总数:
fun(a,b,c)=a(fun(b,c))+ a和b交换b(fun(a,c))+a和c交换c(fun(b,a))
fun(b,c) = b(fun(c))+b和c交换c(fun(b))
fun(c)=1
fun(b)=1
代码实现:
解法1:

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Collections;

public class Solution {
    public ArrayList<String> Permutation(String str) {
       ArrayList<String> result = new ArrayList<String>();
        if(str == null || str.length() == 0){
           return result;
       }
       //防止输入重复如aab,采用set
        HashSet<String> set = new HashSet<String>();
        //递归
        PermutationHelper(str.toCharArray(),0,set);
        result.addAll(set);
        //按字典序打印
        Collections.sort(result);
        return result;
    }
    
    private void PermutationHelper(char[] chars, int index, HashSet<String> set){
       //递归终止条件
        if(index == chars.length){
            set.add(String.valueOf(chars));
            return;
        }
        // for循环和swap的含义:对于“ABC”,
        // 第一次'A' 与 'A'交换,字符串为"ABC", pos为0, 相当于固定'A'
        // 第二次'A' 与 'B'交换,字符串为"BAC", pos为0, 相当于固定'B'
        // 第三次'A' 与 'C'交换,字符串为"CBA", pos为0, 相当于固定'C'
        for(int i = index ;i < chars.length ;i++){
            swap(chars,i,index);
            PermutationHelper(chars,index + 1,set);
            swap(chars,i,index);
        }
    }
    
    private void swap(char[] chars,int i,int j){
        if(i != j){
            char temp = chars[i];
            chars[i] = chars[j];
            chars[j] = temp;            
        }
    }
}

解法2:

**
 * 2、字典序排列算法
 *
 * 可参考解析: http://www.cnblogs.com/pmars/archive/2013/12/04/3458289.html  (感谢作者)
 *
 * 一个全排列可看做一个字符串,字符串可有前缀、后缀。
 * 生成给定全排列的下一个排列.所谓一个的下一个就是这一个与下一个之间没有其他的。
 * 这就要求这一个与下一个有尽可能长的共同前缀,也即变化限制在尽可能短的后缀上。
 *
 * [例]839647521是1--9的排列。1—9的排列最前面的是123456789,最后面的987654321,
 * 从右向左扫描若都是增的,就到了987654321,也就没有下一个了。否则找出第一次出现下降的位置。
 *
 * 【例】 如何得到346987521的下一个
 * 1,从尾部往前找第一个P(i-1) < P(i)的位置
 * 3 4 6 <- 9 <- 8 <- 7 <- 5 <- 2 <- 1
 * 最终找到6是第一个变小的数字,记录下6的位置i-1
 *
 * 2,从i位置往后找到最后一个大于6的数
 * 3 4 6 -> 9 -> 8 -> 7 5 2 1
 * 最终找到7的位置,记录位置为m
 *
 * 3,交换位置i-1和m的值
 * 3 4 7 9 8 6 5 2 1
 * 4,倒序i位置后的所有数据
 * 3 4 7 1 2 5 6 8 9
 * 则347125689为346987521的下一个排列
 *
 */
 
import java.util.ArrayList;
import java.util.Arrays;

public class Solution {
    public ArrayList<String> Permutation(String str) {
        ArrayList<String> list = new ArrayList<String>();
        if(str==null || str.length()==0){
            return list;
        }
        
        char[] chars = str.toCharArray();
        Arrays.sort(chars);
        list.add(String.valueOf(chars));
        
        int len = chars.length;
        while(true){
            int lIndex = len-1;
            int rIndex;
            //从尾部向前查找第一个P(i-1) < P(i)的位置
            while(lIndex >= 1 && chars[lIndex-1]>=chars[lIndex]){
                lIndex--;
            }
            if(lIndex == 0){
                break;
            }    
            //从i位置往后找到最后一个大于P(i-1)的数
            rIndex = lIndex;
            while(rIndex<len && chars[rIndex]>chars[lIndex-1]){
                rIndex++;
            }
            
            //和P(i-1)交换
            swap(chars,lIndex-1,rIndex-1);
            //倒序P(i)位置后所有数据
            reverse(chars,lIndex);
            list.add(String.valueOf(chars));
        }
        return list;
    }
    
    private void reverse(char[] chars,int k){
        if(chars==null || chars.length<=k)
            return;
        int len = chars.length;
        for(int i=0;i < (len-k)/2;i++){
            int m = k+i;
            int n = len-1-i;
            if(m <= n){
                swap(chars,m,n);
            }
        }
    }

    private void swap(char[] chars,int i,int j){
        char temp = chars[i];
        chars[i] = chars[j];
        chars[j] = temp;
    }
}

29. 数组中出现次数超过一半的数字

题目描述:
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。如果不存在则输出 0。
思路:
将首次出现的数 count+1,与之后的数进行比较,相等则+1,否则-1,最后进行校验是否超过长度的一半。
思路优化:
用preValue记录上一次访问的值,count表明当前值出现的次数,如果下一个值和当前值相同那么count++;如果不同count–,减到0的时候就要更换新的preValue值了,相当于之前的两两相抵,因为如果存在超过数组长度一半的值,那么最后preValue一定会是该值。
代码实现:
解法1:

public class Solution {
    public int MoreThanHalfNum_Solution(int [] array) {
        int preValue = array[0];
        int count = 1;
        for(int i = 1;i < array.length;i++){
            if(array[i] == preValue){
                count++;
            }else{
                count--;
                if(count == 0){
                    preValue = array[i];
                    count = 1;
                }
            }
        }
        
        //统计出现次数,用于判定是否大于一半
        int times = 0;
        for(int i : array){
            if(i == preValue){
                times++;
            }
        }
        
        //大于一半则输出       
        return times > array.length/2? preValue:0;
    }
}

解法2:哈希法
先遍历一遍数组,在HashMap中存每个元素出现的次数,然后再遍历一次数组,找出众数。

import java.util.*;

public class Solution {
    public int MoreThanHalfNum_Solution(int [] array) {
        if(array.length == 0){
            return 0;
        }

        int len = array.length;
        int threshold = len/2;
        Map<Integer,Integer> map = new HashMap<>();
        for(int i = 0; i < len; i++){
            if(!map.keySet().contains(array[i])){
                map.put(array[i],1);
            }else{
                map.put(array[i],map.get(array[i])+1);
            }
        }

        for(Integer key: map.keySet()){
            if(map.get(key) > threshold){
                return key;
            }
        }

        return 0;
    }
}

解法3:排序法
先将数组排序,然后可能的众数在数组中间,判断一下是否符合,复杂度较高,不贴代码了。


30. 找出最小的K个数 (未解)

题目描述:
输入 n 个整数,找出其中最小的 K 个数。
思路:
先将前 K 个数放入数组,进行堆排序,若之后的数比它还小,则进行调整。
代码实现:


31. 连续子数组的最大和

题目描述:
输入一个整型数组,数组中有正数也有负数,数组中一个或连续的多个整数组成一个子数组,求连续子数组的最大和 。
思路:
动态规划,最大子数组的和一定是由当前元素和之前最大连续子数组的和叠加在一起形成的,因此需要遍历n个元素,看看当前元素和其之前的最大连续子数组的和能够创造新的最大值。
代码实现:

public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
    	//纪录最大值
        int max = array[0];
        //记录最大连续子数组
        int dp = array[0];
        //最大连续子数组和后一元素相加之后和当前最大连续子数组相比较
        for(int i = 1;i < array.length;i++){
            int newMax = array[i] + dp;
            if(newMax > array[i]){
                dp = newMax;
            }else{
                dp = array[i];
            }
            //记录最大值
            if(dp > max){
                max = dp;
            }
        }
        return max;
    }
}

32. 从 1 到非负整数 n 中 1 出现的次数

题目描述:
输入一个整数 n,求从 1 到整数 n 的十进制表示中 1 出现的次数 。
思路:
若百位上数字为 0,百位上可能出现 1 的次数由更高位决定;若百位上数字 为 1,百位上可能出现 1 的次数不仅受更高位影响还受低位影响;若百位上数字大 于 1,则百位上出现 1 的情况仅由更高位决定。
代码实现:


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值