《剑指offer》

二维数组中的查找

题设条件
在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
思路分析
矩阵是有序的,从左下角来看,向上数字递减,向右数字递增。因此从左下角开始查找,当要查找数字比左下角数字大时,右移;要查找数字比左下角数字小时,上移。同样也可以从右上角开始查找。
代码实现
//二维数组中的查找
// 测试用例类型:二维数组中包含查找的数字、不包含查找的数字、空指针
public class Solution3 {
    public boolean Find(int target, int [][] array) {
        if (array == null) 
            return false;

        // 获取数组行列数
        int rows = array.length;
        int cols = array[0].length;

        // 从左下角开始遍历
        int row = rows-1, col = 0;
        while (row >=0 && col < cols) {
            if (array[row][col] < target) {// 目标值大于当前数组元素,向右查找
                col++;
            } else if (array[row][col] > target) {// 目标值小于当前数组元素,向上查找
                row--;
            } else {// 查找到
                return true;
            }
        }
        return false;// 查找失败
    }
}

替换空格

题设条件
请实现一个函数,将一个字符串中的空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
思路分析
如果在新开辟的空间进行替换,则只需从前往后逐个字符写入,遇到空格则转换;如果在原始空间进行替换,则需要从后往前替换空格,避免反复移动。
代码实现
在新空间替换
public String replaceSpace(StringBuffer str) {
    int len = str.length();
    StringBuffer sb = new StringBuffer();

    // 逐个遍历字符,追加到sb
    for (int i = 0; i < len; i++) {
        char ch = str.charAt(i);
        if (ch == ' ')
            sb.append("%20");
        else
            sb.append(ch);
    }

    return sb.toString();
}

在原始空间进行替换

public static String replaceSpace(StringBuffer str) {
    // 首先,遍历数组,确定空格个数
    int oldlen = str.length();
    int spacenum = 0;
    for (int i = 0; i < oldlen; i++) {
        if (str.charAt(i) == ' ')
            spacenum++;
        else
            continue;
    }

    // 然后,根据空格个数,计算新字符串长度,扩容原始字符串空间
    int newlen = oldlen + spacenum * 2;
    str.setLength(newlen);

    // 接着,依次从后往前移动字符,遇到空格则替换
    int newIndex = newlen-1;
    int oldIndex = oldlen - 1;

//      while (oldIndex >= 0 && newIndex >= 0) {
    while (oldIndex != newIndex) {// 当两个索引相遇时,表示空格已经替换完毕,不需要再移动前面的字符
        if (str.charAt(oldIndex) == ' ') {
            str.setCharAt(newIndex--, '0');
            str.setCharAt(newIndex--, '2');
            str.setCharAt(newIndex--, '%');
            oldIndex--; // 容易出错
        } else {
            str.setCharAt(newIndex--, str.charAt(oldIndex--));
        }
    }

    return str.toString();
}
复杂度分析
时间复杂度O(n),空间复杂度O(1)

从尾到头打印链表

题设条件
输入一个链表,从尾到头打印链表每个节点的值。
思路分析
使用递归的方法
代码实现
// 方法1,使用递归的方法
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
    ArrayList<Integer> list = new ArrayList<Integer>();

    if (listNode != null) {// 递归进行的条件
        list = printListFromTailToHead(listNode.next);
        list.add(listNode.val);
    } else {// 递归结束
        ;
    }
    return list;
}
复杂度分析
时间复杂度O(n),空间复杂度O(n)

利用栈的FILO特性

思路分析
先从头遍历链表,元素依次入栈
代码实现
// 方法2,利用栈实现
public ArrayList<Integer> printListFromTailToHead2(ListNode listNode) {
    ArrayList<Integer> list = new ArrayList<Integer>();
    Stack<Integer> stack = new Stack<Integer>();

    while (listNode != null) {
        stack.push(listNode.val);
        listNode = listNode.next;
    }

    while (!stack.isEmpty())
        list.add(stack.pop());

    return list;
}
复杂度分析
时间复杂度O(n),空间复杂度O(n)

重建二叉树

题设条件
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
思路分析
前序遍历的第一个元素为根元素,在中序遍历中查找根元素的索引rootIndex。通过rootIndex拆分中序遍历结果in,得到左子树的中序遍历leftin和右子树的中序遍历rightin;通过rootIndex拆分前序遍历结果pre,得到左子树的前序遍历leftpre和右子树的前序遍历rightpre。递归方法进行以上操作,直到数组长度为1时返回叶子节点,或为0时返回空节点。
代码实现
public static TreeNode reConstructBinaryTree(int [] pre,int [] in) {
    // 数组长度大于1时进行递归,等于1时返回叶子节点,小于1时返回null;
    int len = pre.length;// 数组长度,两个数组等长
    if (len < 1)
        return null;

    if (len == 1)
        return new TreeNode(pre[0]);

    // 递归进行条件
    int rootIndex = 0;// 根节点在中序遍历中的索引
    TreeNode root = null;// 根当前子树的根节点,作为结果返回

    // 找出rootIndex
    for (int i = 0; i < in.length; i++) {
        if (in[i] == pre[0]) {
            rootIndex = i;
            break;
        }
    }

    // 左子树有rootIndex个元素,右子树部分有len - 1 - rootIndex个元素
    int[] leftpre = Arrays.copyOfRange(pre, 1, rootIndex+1);        // 前序遍历中左子树部分,索引[1, rootIndex]
    int[] rightpre = Arrays.copyOfRange(pre, rootIndex + 1, len);   // 前序遍历的右子树分,索引[rootIndex+1, len-1]
    int[] leftin = Arrays.copyOfRange(in, 0, rootIndex);            // 中序遍历的左子树部分,索引[0, rootIndex-1]
    int[] rightin = Arrays.copyOfRange(in, rootIndex + 1, len);     // 中序遍历的右子树部分,索引[rootIndex+1, len - 1]

    TreeNode rootNode = new TreeNode(pre[0]);
    rootNode.left = reConstructBinaryTree(leftpre, leftin);
    rootNode.right = reConstructBinaryTree(rightpre, rightin);
    return rootNode;
}

两个栈实现队列

题设条件
用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
代码实现
import java.util.Stack;

public class Solution {
    //用stack1作为主要栈,入队push操作都针对这个栈
    //用stack2作为辅助栈,出队pop操作时先判断stack2是否为空。若stack2为空,则现将stack1栈元素压入stack2,然后stack2出栈;
    //若stack2不为空,则stack2直接出栈

    Stack<Integer> stack1 = new Stack<Integer>();
    Stack<Integer> stack2 = new Stack<Integer>();

    public void push(int node) {
        stack1.push(node);
    }

    public int pop() {
        int res = 0;

        if (stack2.isEmpty()) {
            while (!stack1.isEmpty())
                stack2.push(stack1.pop());
            res = stack2.pop();
        } else {
            res = stack2.pop();
        }

        return res;
    }
}
复杂度分析
最坏情况为n个元素先依次进栈,然后依次出栈。时间复杂度为O(n),空间复杂度为O(n)

斐波那契数列

题设条件
大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项。
n<=39
代码实现
public class Solution {
    public int Fibonacci(int n) {
        int first = 0, second = 1;// 0-1项
        int tmp = 0;

        for (int i = 1; i <= n; i++) {// 2-(n-1)项
            tmp = first + second;
            first = second;// first所指向的为新生成的项
            second = tmp;
        }
        return first;
    }
}

跳台阶

题设条件
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
思路分析
当位于第n级台阶时,上一步可以是第n-1级台阶,也可以是第n-2级台阶,所以等于前两种情况的加和。当n==1时,有1种情况;当n==2时,有2中情况。
代码实现
public class Solution {
    public int JumpFloor(int target) {
        if (target == 1)
            return 1;
        else if (target == 2)
            return 2;
        else
            return JumpFloor(target - 1) + JumpFloor(target - 2);
    }
}

变态跳台阶

题设条件
一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
思路分析
n==1时,只有一种情况,0-1;n == 2时,有两种情况0-2、0-1-2,即从第0级跳过来和从第1级跳过来;n==3时,有四种情况0-3、0-1-3、0-2-3、0-1-2-3,即分别从第0级、1级、2级跳过来……依次类推,在第n级台阶时,可能从之前每一集台阶跳过来。
设n==0时,有一种情况,则 f(n) = f(0) + f(1) + …… + f(n-1)
其中 f(0)=1,f(1)=1
有如下递推公式:
f(n) = f(0) + f(1) + …… + f(n-2) + f(n-1)
f(n-1)=f(0) + f(1) + …… + f(n-2)

所以,f(n) = 2*f(n-1)。

代码实现
public int JumpFloorII(int target) {
    if (target == 0)
        return 1;
    else
        return (int)Math.pow(2, target-1);
}

利用移位运算的性质,改进版

public int JumpFloorII(int target) {
    if (target == 0)
        return 1;
    else
        return 1 << (target-1);
}

矩形覆盖问题

题设条件
我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?
思路分析
n==1时,有1种方法;n==2时,有2种方法;n==3时,有3种方法;……;n==k时,取决于第一个矩形是横着放还是竖着放。所以,
f(n) = f(n-1) + f(n-2), f(1)=1, f(2)=2
代码实现
public int RectCover(int target) {
    if (target == 0)
        return 0;
    else {
        int first = 1, second = 1;
        int sum = 0;
        for (int i=0; i< target; i++) {
            sum = first + second;
            first = second;
            second = sum;
        }
        return first;            
    }
}

二进制中1的个数

题设条件
输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
思路分析

数值的整数次方

题设条件
给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。
思路分析
exponent>0时,每次循环乘以base;exponent<0时,每次循环除以base。
代码实现
public double Power(double base, int exponent) {
    double res = 1.0;

    if (exponent > 0) {
        while (exponent > 0) {
            res *= base;
            exponent--;
        }
    } else if (exponent < 0) {
        while (exponent < 0) {
            res /= base;
            exponent++;
        }
    } else {
        ;
    }

    return res;
}

反转链表

代码实现
public ListNode ReverseList(ListNode head) {
 //   if (head == null || head.next == null)// 链表为null或长度为1时,直接返回
   //     return head;

    ListNode pre = null, curr = head, succ = null;
    while (curr != null) {
        succ = curr.next;
        curr.next = pre;

        pre = curr;
        curr = succ;
    }

    head = pre;
    return head;
}

合并两个排序单链表

题设条件
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
代码实现
非递归、原地合并,时间复杂度O(m+n),空间复杂度O(1)
public ListNode Merge2(ListNode list1,ListNode list2) {
    if (list1 == null && list2 == null) {// 处理链表为空情况
        return null;
    } else if (list1 == null) {
        return list2;
    } else if (list2 == null) {
        return list1;
    }

    // 链表均非空
    ListNode head = new ListNode(0);// 头结点
    ListNode curr1 = list1, curr2 = list2, curr = head;// 移动指针

    while (curr1 != null && curr2 != null) {
        if (curr1.val < curr2.val) {
            curr.next = curr1;

            curr = curr.next;
            curr1 = curr1.next;
        } else {
            curr.next = curr2;

            curr = curr.next;
            curr2 = curr2.next;
        }
    }

    if (curr1 != null)
        curr.next = curr1;
    if (curr2 != null)
        curr.next = curr2;

    return head.next;
}

非递归、创建新链表,时间复杂度O(m+n),空间复杂度O(m+n)

public ListNode Merge(ListNode list1,ListNode list2) {
    ListNode head = new ListNode(0);
    ListNode curr1 = list1, curr2 = list2, curr = head;

    while (curr1 != null && curr2 != null) {
        if (curr1.val > curr2.val) {
            curr.next = new ListNode(curr2.val);

            curr = curr.next;
            curr2 = curr2.next;
        } else {
            curr.next = new ListNode(curr1.val);

            curr = curr.next;
            curr1 = curr1.next;
        }
    }

    while (curr1 != null) {
        curr.next = new ListNode(curr1.val);

        curr = curr.next;
        curr1 = curr1.next;
    }

    while (curr2 != null) {
        curr.next = new ListNode(curr2.val);

        curr = curr.next;
        curr2 = curr2.next;
    }

    return head.next;
}

递归版本,时间复杂度O(m+n),空间复杂度O(n*n)

二叉树的镜像

题设条件
操作给定的二叉树,将其变换为源二叉树的镜像。
这里写图片描述
代码实现
public void Mirror(TreeNode root) {
    if (root == null || root.left == null && root.right == null) {// 当前节点null时,结束;左右子树均为空时,结束递归
        return;
    }

    // 递归进行条件

    // 交换左右子树
    TreeNode tmp = null;
    tmp = root.left;
    root.left = root.right;
    root.right = tmp;

    Mirror(root.left);
    Mirror(root.right);
}

二进制中1的个数

题设条件
输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
思路分析
对于正数,存储过程中按源码存储,直接对2求余数即可得到源码表示中的“1”;对于负数,按补码存储,题设条件是找出补码中“1”的个数,所以也可以直接对2求余数得到补码形式中的“1”。每求一次余数后,需要对数字除以2,正数右移一位即可, 负数需要无符号右移。注意,负数对2求余数得到负数,即首次求余数得到负数,所以不能用是否等于1来判断。
代码实现
public int NumberOf1(int n) {
    int count = 0;
    while (n != 0) {
        if (n % 2 != 0) // 负数求余得到负数
            count++;
        n = n >>> 1;    // 负数需要无符号右移
    }

    return count;
}

除了不停地对n求余、移位外,还可以通过 位与 运算检测不同位上的1

代码实现
public int NumberOf1(int n) {
    int count = 0;
    int mask = 1;

    for (int i = 1; i <= 32; i++) {
        if ((mask & n) != 0)
            count++;

        mask = mask << 1;
    }

    return count;
}

包含min函数的栈

题设条件
定义栈的数据结构,请在该类型中实现一个能够得到栈最小元素的min函数。
思路分析
用一个辅助栈minStack存储元素最小值,栈顶元素代表最小值;用numStack存储每次入栈的元素。当一个元素入栈numStack时,若当前入栈元素小于等于minStack的栈顶,则将该元素入栈minStack;否则,不如minStack栈。numStack出栈时,若出栈元素等于minStack栈顶,则minStack跟着出栈。
代码实现
import java.util.Stack;

public class Solution {
    Stack<Integer> minStack = new Stack<>();// 存储最小值
    Stack<Integer> numStack = new Stack<>();// 存储入栈数据

    public void push(int node) {
        numStack.push(node);

        if (minStack.isEmpty() || minStack.peek() >= node)// 入栈元素小于等于当前最小值时,元素入minStack栈
            minStack.push(node);
    }

    public void pop() {
        if (numStack.peek() == minStack.peek())
            minStack.pop();

        numStack.pop();
    }

    public int top() {
        return numStack.peek();
    }

    public int min() {
        return minStack.peek();
    }
}

从上往下打印二叉树

题设条件
从上往下打印出二叉树的每个节点,同层节点从左至右打印。
思路分析
二叉树的层次遍历,借助一个队列存储每层待访问节点
代码实现
public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
    Queue<TreeNode> queue = new LinkedList<TreeNode>();
    ArrayList<Integer> res = new ArrayList<>();

    if (root == null)
        return res;

    queue.add(root);
    while (!queue.isEmpty()) {
        TreeNode node = queue.remove();// 出队元素
        if (node.left != null)
            queue.add(node.left);
        if (node.right != null)
            queue.add(node.right);

        res.add(node.val);
    }

    return res;
}

链表中倒数第k个节点

题设条件
输入一个链表,输出该链表中倒数第k个结点。
思路分析
用双指针法,left、right指针初始位置均指向第一个节点。第一步,left保持不动,right后移k步;第二步,两个指针同时后移,直到right指向为空,此时left所指向的即为倒数第k个节点。
代码实现
public ListNode FindKthToTail(ListNode head,int k) {
    ListNode left = head, right = head;

    for (int i = 0; i < k; i++) {
        if (right == null)
            return null;
        else
            right = right.next;
    }

    while (right != null) {
        left = left.next;
        right = right.next;
    }

    return left;
}

调整数组顺序使奇数位于偶数前面

题设条件
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
思路分析
可以借鉴稳定的排序方法,如冒泡排序、归并排序、直接插入排序等;也可以新开辟一个等长数组,进行元素复制。
代码实现
新开辟一个等长数组,进行元素复制。时间复杂度O(n),空间复杂度O(n)
public void reOrderArray(int [] array) {
    int len = array.length;
    int[] tmp = new int[len];// 辅助数组

    // 首先,对原始数组元素进行复制
    for (int i =0; i < len; i++) {
        tmp[i] = array[i];
    }

    // 然后,第一次遍历辅助数组tmp,将奇数元素复制到原始数组前半部分
    int index = 0;// array数组的索引
    for (int i = 0; i < len; i++) {
        if (tmp[i] % 2 != 0) {// 负奇数求余得到负数,所以不能用等于1来判断
            array[index++] = tmp[i];
        } else {
            continue;
        }
    }

    // 第二次遍历辅助数组,将偶数元素复制到原始数组的后半部分
    for (int i = 0; i < len; i++) {
        if ((tmp[i] & 1) == 0) {// 利用位与运算,取二进制的最低位
            array[index++] = tmp[i];
        } else {
            continue;
        }
    }

    return;
}

借鉴直接插入排序的思想,将奇数插入到前面

public void reOrderArray(int [] array) {
    int len = array.length;

    for (int i = 1; i < len; i++) {         // 外层索引,指向待插入的元素
        if ((array[i] & 1) == 0) {  // 若当前待插入元素为偶数,则跳过
            continue;
        } else {    // 对奇数元素进行插入处理
            int tmp = array[i];
            int j = 0;
            for(j = i-1; j >= 0 && (array[j] % 2 == 0); j--) {      // 内层索引,指向待检查的元素
                array[j+1] = array[j];
            }
            array[j+1] = tmp;
        }
    }
}

借鉴冒泡排序思想,若当前元素为偶数,则交换相邻元素。交换的条件是当前元素为偶数,下一个元素为奇数

public void reOrderArray(int [] array) {
    int len = array.length;

    for (int i = 1; i < len; i++) { // 外层循环,控制趟数
        for (int j = 0; j < len - i; j++) {
            if (array[j]%2 == 0 && array[j+1]%2 != 0) {
                 int tmp = array[j];
                 array[j] = array[j+1];
                 array[j+1] = tmp;
            }
        }
    }

}

借鉴归并排序的思想

public void reOrderArray(int [] array) {
    split(array, 0, array.length-1);
}
public void split(int[] array, int start, int end) {
    if (start < end) {
        int mid = (start +end) >> 1;
        split(array, start, mid);
        split(array, mid+1, end);
        merge(array, start, mid, mid+1, end);
    } else {
        return;
    }
}

public void merge(int[] array, int start1, int end1, int start2, int end2) {
    int len = end2 - start1 + 1;
    int[] tmp = new int[len];

    // 首先,将原始数组的元素归并到辅助数组:先复制array1的奇数元素,再复制array2的奇数元素,接着复制array1的偶数元素以及array2的偶数元素
    int i = 0;
    for (int j = 0; j < len; j++) {
        if (array[start1 + j] % 2 != 0) {
            tmp[i++] = array[start1 + j];
        }
    }
    for (int j = 0 ; j < len; j++) {
        if (array[start1 + j] % 2 == 0) {
            tmp[i++] = array[start1 + j];
        }
    }

    // 然后,将归并后的元素复制到原始数组
    for (int j = 0; j < len; j++) {
        array[start1 + j] = tmp[j];
    }
}

顺时针打印矩阵

题设条件
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下矩阵: 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。
思路分析
用两个定位点来定位待打印的范围。本题分别用左上角和右下角来定位,每打印完一圈,左上角、右下角的索引均向内移动一次,外层循环的条件是rowMin 如果行列均为偶数,则外层循环结束后rowMin==rowMax && colMin==colMax,剩余一个中心点需要额外处理;如果行为奇数,则外层循环结束后colMin
代码实现
public ArrayList<Integer> printMatrix(int[][] matrix) {
    ArrayList<Integer> res = new ArrayList<>();

    if (matrix == null)
        return res;

    int rows = matrix.length, cols = matrix[0].length; // 获取行列数
    int rowMin = 0, colMin = 0, rowMax = rows - 1, colMax = cols - 1; // 定义行的最大最小值、列的最大最小值,用来标识待打印的左上角和右下角

    while (rowMin < rowMax && colMin < colMax) { // 左上角和右下角不重合
        for (int i = colMin; i < colMax; i++) {// 从左到右打印第一行
            res.add(matrix[rowMin][i]);
        }

        for (int i = rowMin; i < rowMax; i++) {// 从上到下打印最后一列
            res.add(matrix[i][colMax]);
        }

        for (int i = colMax; i > colMin; i--) {// 从右到左打印最后一行
            res.add(matrix[rowMax][i]);
        }

        for (int i = rowMax; i > rowMin; i--) {// 从下到上打印第一列
            res.add(matrix[i][colMin]);
        }

        rowMin++;
        colMin++;
        rowMax--;
        colMax--;

    }

    // 处理只剩下一行或一列的情况
    if (colMin < colMax && rowMin == rowMax) {  // 只剩下一行,从左至右打印
        for (int i = colMin; i <= colMax; i++)
            res.add(matrix[rowMin][i]);

    } else if (rowMin < rowMax && colMin == colMax) {   // 只剩下一列,从上至下打印
        for (int i = rowMin; i <= rowMax; i++)
            res.add(matrix[i][colMin]);

    } else if (rowMin == rowMax && colMin == colMax) { // 只剩下一个中心点
        res.add(matrix[rowMin][colMin]);

    }

    return res;
}

树的子结构

题设条件
输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
思路分析
首先从根节点开始检索。若根节点值相等,则开始从根节点匹配;若根节点不相等或根节点匹配失败,则递归检索左子树;若左子树检索失败,则递归检索右子树。
匹配过程也是递归,递归结束条件有三个
1)root2==null, 2)root1==null && root2!=null, 3)root1.val!=root2.val
若root1.val==root2.val,则递归匹配左右子树。
代码实现
public class Solution3 {
    public boolean HasSubtree(TreeNode root1,TreeNode root2) {
        boolean res = false;

        if (root1 != null && root2 != null) {
            if (root1.val == root2.val) {   // 开始匹配
                res = tree1HasTree2(root1, root2);
            }

            if (!res) { // 根节点匹配失败,检索左子树
                res = HasSubtree(root1.left, root2);
            }

            if (!res) { // 左子树也匹配失败,检索右子树
                res = HasSubtree(root1.right, root2);
            }
        }

        return res;
    }

    public boolean tree1HasTree2(TreeNode root1, TreeNode root2) {
        // 递归结束的三种情况:1)root2==null, 2)root1==null && root2!=null, 3)root1.val!=root2.val
        if (root2 == null)
            return true;
        else if (root2 != null && root1 == null)
            return false;
        else if (root1.val != root2.val)
            return false;
        else // 递归进行
            return tree1HasTree2(root1.left, root2.left) && tree1HasTree2(root1.right, root2.right);
    }
}

栈的压入、弹出序列

题设条件
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)
思路分析
借用一个辅助的栈stack。如果出栈序列的当前元素是栈顶元素,则直接弹出;如果出栈序列的当前元素不是栈顶元素,则入栈;如果栈为空,则入栈。
代码实现
public boolean IsPopOrder(int [] pushA,int [] popA) {
    if (pushA.length != popA.length)
        return false;

    int len = pushA.length;
    Stack<Integer> stack = new Stack<>();   // 辅助栈

    int outIndex = 0, inIndex = 0; // 出栈、入栈序列的索引
    for (inIndex = 0; inIndex < len; inIndex++) {// 遍历入栈序列
        stack.push(pushA[inIndex]);

        while(!stack.isEmpty() && stack.peek()==popA[outIndex]) {// 如果当前栈顶元素与当前出栈元素相等,则出栈
            stack.pop();
            outIndex++;
        }
    }

    return stack.isEmpty();
}

二叉搜索树的后序遍历序列

题设条件
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。
思路分析
对于后序遍历序列,最后一个节点是根节点,根节点可以将其之前的序列分为两部分,用递归的方法不断拆分序列数组。递归结束条件:1)序列长度为1时返回true;2)拆分后的子序列中存在和根节点不满足大小关系的元素,返回false。
代码实现
public class Solution3 {
    public boolean VerifySquenceOfBST(int [] sequence) {
        if (sequence == null || sequence.length == 0)
            return false;
        else if (sequence.length == 1)
            return true;
        else
            return VerifySquenceOfBST(sequence, 0, sequence.length-1);
    }

    public boolean VerifySquenceOfBST(int[] sequence, int start, int end) {
        // 递归结束条件
        if (start == end) {
            return true;
        }

        int root = sequence[end];   // 找到当前子树的根节点
        int index = start;  // 代表右子树的子序列第一个索引

        // 找出左右子树的分割点
        for (index = start; index < end; index++) {
            if (sequence[index] < root)
                continue;
            else
                break;
        }
        for (int i = index; i < end; i++) {
            if (sequence[i] > root)
                continue;
            else
                return false;
        }

        if (index == start) { // 只存在右子树
            return VerifySquenceOfBST(sequence, index, end-1);
        } else if (index == end) { // 只存在左子树
            return VerifySquenceOfBST(sequence, start, index-1);
        } else { // 左右子树均存在
            return VerifySquenceOfBST(sequence, start, index-1) && VerifySquenceOfBST(sequence, index, end);            
        }
    }
}

复杂链表的复制

题设条件
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
思路分析
分三步走:第一步复制每个节点,并插入到对应节点后面;第二步,遍历链表,复制每个节点的特殊指针;第三部,拆分链表。
这里写图片描述
代码实现
class RandomListNode {
    int label;
    RandomListNode next = null;
    RandomListNode random = null;

    RandomListNode(int label) {
        this.label = label;
    }
}

public class Solution3 {
    public RandomListNode Clone(RandomListNode pHead) {
        if (pHead == null)
            return null;

        // 第一步,复制每个节点,插入到原节点后面
        RandomListNode curr = pHead;
        while (curr != null) {
            RandomListNode node = new RandomListNode(curr.label);

            node.next = curr.next;
            curr.next = node;

            curr = node.next;// 注意,node节点已经插入到了curr后面
        }

        // 第二步,复制每个节点的兄弟连接
        curr = pHead;
        while (curr != null) {
            if (curr.random != null)
                curr.next.random = curr.random.next;    // 容易出错,random域可能为null
            curr = curr.next.next;;
        }

        // 第三步,拆分链表
        RandomListNode newHead = pHead.next;
        RandomListNode curr1 = pHead, curr2 = newHead;
        while (curr2.next != null) {    // 存在后继节点,至少两个
            curr1.next = curr1.next.next;
            curr2.next = curr2.next.next;

            curr1 = curr1.next;
            curr2 = curr2.next;
        }
        // 对最后两个节点进行处理,即curr2.next==null时
        curr1.next = null;
        curr2.next = null;

        return newHead;
    }
}

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

题设条件
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
代码实现
public int MoreThanHalfNum_Solution(int [] array) {
    if (array == null || array.length == 0)
        return 0;

    int len = array.length;
    int ele = 0;
    int count = 0;

    // 找出可能的众数
    for (int i = 0; i < len; i++) {
        if (count == 0) {
            ele = array[i];
            count++;
        } else if (array[i] == ele) {
            count++;
        } else {
            count--;
        }
    }

    // 确认是否是众数
    count = 0;
    for (int arr : array)
        if (arr == ele)
            count++;

    if (count > (len>>1))
        return ele;
    else
        return 0;
}

最小的k个数

题设条件
输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。
思路分析
利用排序二叉树集合TreeSet,利用TreeSet的有序特性,只保存前k个数字
代码实现
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
    ArrayList<Integer> res = new ArrayList<>();

    if (input == null || input.length < k)
        return res;

    TreeSet<Integer> set = new TreeSet<>();
    for (int item : input) {
        set.add(item);

        // 若当前容量超过k,则移除最大元素
        if (set.size() > k)
            set.remove(set.last());
    }

    for (int item : set)
        res.add(item);

    return res;
}
复杂度分析
时间复杂度O(n*log(k)),空间复杂度O(k)

也可以利用冒泡排序的思想,排序k趟即可将最小的k个数排到前面

代码实现
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
    ArrayList<Integer> res = new ArrayList<>();

    if (input == null || input.length < k)
        return res;

    int len = input.length;
    for (int i = 0; i < k; i++) { // 外层循环每进行一次,排序完成一个元素
        for (int j = len-1; j > i; j--) {
            if (input[j] < input[j-1]) {
                int tmp = input[j];
                input[j] = input[j-1];
                input[j-1] = tmp;
            }
        }
    }

    for (int i = 0; i < k; i++)
        res.add(input[i]);

    return res;
}
复杂度分析
时间复杂度O(n*k),空间复杂度O(1)

连续子数组的最大和

题设条件
在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。
代码实现
public int FindGreatestSumOfSubArray(int[] array) {
    if (array == null || array.length < 1)
        return Integer.MIN_VALUE;

    int sum = 0; // 存储当前子序列的累加和
    int maxSum = Integer.MIN_VALUE; // 存储最大和
    int len = array.length;

    for (int i = 0; i < len; i++) {
        sum += array[i];

        maxSum = Math.max(maxSum, sum);
        if (sum < 0)
            sum = 0;
    }

    return maxSum;

}

把数组排成最小的数

题设条件
输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。
思路分析
要使拼接输出的字符串表示的数字最小,需要对原始数组进行排序。对原始数组的元素a、b,应该定义一种比较规则,使得若a
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

public class Solution {
    public String PrintMinNumber(int [] numbers) {
        if (numbers == null || numbers.length == 0)
            return "";

        ArrayList<String> arrlist = new ArrayList<>();

        // 将数字转换为字符串,存放在ArrayList中
        for (int num : numbers)
            arrlist.add(new Integer(num).toString());

        // 对ArrayList中的字符串排序
        Collections.sort(arrlist, new Comparator<String>() {
            public int compare(String o1, String o2) {
                String str1 = o1+o2;
                String str2 = o2+o1;
                return str1.compareTo(str2);
            };
        });

        // 对排序后的字符串进行拼接输出
        StringBuffer sb = new StringBuffer();
        for (String str : arrlist)
            sb.append(str);

        return sb.toString();
    }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值