【剑指Offer】知识迁移能力

数字在排序数组中出现的次数

题目描述

统计一个数字在排序数组中出现的次数。

分析

由于数组是排序的,所以使用二分查找法可以降低时间成本,时间复杂度为O(log(n))。
数字K出现的次数可能是0次,1次,大于等于2次。
情况一:数组中存在K
首先使用getFirstK()二分查找第一个K,返回其下标firstK。在使用getLastK()二分查找最后一个K,返回其下标lastK。总个数为lasrK-firstK+1。
如果lastK=firstK,说明只出现一次。
情况二:数组中不存在K
使用首先使用getFirstK()二分查找第一个K,未寻得,返回-1。即可说明数组中不存在K,故没有必要执行getLastK()。

在数组中二分查找第一个K时,mid=(start+end)/2,当array[mid]=K时,有两种可能:

  1. 在下标mid之前的元素也有K,则array[mid-1]=K。递归继续查找K;
  2. 仅array[mid]=K,array[mid-1]<K,则返回mid。

查找最后一个K同理。

Java代码

public class Solution {
    public int GetNumberOfK(int [] array , int k) {
        int len = array.length;
        if(len==0 || array==null)
            return 0;
        int firstK = getFirstK(array, k, 0, len-1);
        if(firstK != -1){
            int lastK = getLastK(array, k, 0, len-1);
            return lastK - firstK + 1;
        }
        return 0;
    }
    public int getFirstK(int[] array, int k, int start, int end){
        if(start > end)
            return -1;
        int mid = (start+end) >> 1;
        if(array[mid] > k)
            return getFirstK(array, k, start, mid-1);
        else if(array[mid] < k)
            return getFirstK(array, k, mid+1, end);
        else{
            if(mid-1>=start && array[mid-1]==k)
                return getFirstK(array, k, start, mid-1);
            else
                return mid;
        }
    }
    
    public int getLastK(int[] array, int k, int start, int end){
        if(start>end)
            return -1;
        int mid = (start+end) >> 1;
        if(array[mid] > k)
            return getLastK(array, k, start, mid-1);
        if(array[mid] < k)
            return getLastK(array, k, mid+1, end);
        else{
            if(mid+1<=end && array[mid+1]==k)
                return getLastK(array, k, mid+1, end);
            else
                return mid;
        }
    }
}

二叉树的深度

题目描述

输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
树的结点定义如下:

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

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

分析

如果一棵树只有根结点,其深度为1;
如果根节点只有左子树,没有右子树,则深度为(左子树的深度 + 1);
如果根节点只有右子树,没有左子树,则深度为(右子树的深度 + 1);
如果根节点既有左子树又有右子树,则深度为(Max(左子树的深度,右子树的深度) + 1)。
使用递归的思想很容易实现。

Java代码

public class Solution {
    public int TreeDepth(TreeNode root) {
        if(root == null)
            return 0;

        int leftDepth = TreeDepth(root.left);
        int rightDepth = TreeDepth(root.right);

        return Math.max(leftDepth, rightDepth) + 1;
    }
}

判断是否为平衡二叉树

题目描述

输入一棵二叉树,判断该二叉树是否是平衡二叉树。

分析

由于平衡二叉树中不是只比较根结点的左右子树的深度就可以,而是要保证任意一个结点的左右子树的深度差值不超过1。可以采用后续遍历的方法。
采用后续遍历的好处就是,每遍历一个结点,我们已经遍历其左右子树,直到了左右子树的深度差,如果是平衡的就记录下最大深度,若不是平衡的则记录为-1。
这样每个结点只要遍历一次即可。

Java代码

public class Solution {
    public boolean IsBalanced_Solution(TreeNode root) {
        if(isBalanced(root) != -1)
            return true;
        return false;
    }
    public int isBalanced(TreeNode root){
        if(root == null)
            return 0;
        int leftDepth = isBalanced(root.left);
        int rightDepth = isBalanced(root.right);
        if(leftDepth>=0 && rightDepth>=0){
            int d = leftDepth - rightDepth;
            if(d>=-1 && d<=1)
                return Math.max(leftDepth, rightDepth) + 1; 
        }
        return -1;
    }
}

数组中只出现一次的数字

题目描述

一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

分析

已知两个相同的数字做“异或”操作结果为0,如果数组中只有一个出现一次的数字,将所有数字做异或,最后得到的结果就是该数字。
本题中有两个只出现一次的数字,根据上面的思路可以将数组分成两个子数组,每一个数组中包含一个只出现一次的数字,则可以按照上述思路找到这两个数字。

Java代码

import java.util.*;
public class Solution {
    public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
        for(int x : array){
            int cnt = map.getOrDefault(x, 0);
            cnt = cnt ^ 1;
            map.put(x, cnt);
        }
        int flag = 0;
        Iterator<Map.Entry<Integer, Integer>> ite = map.entrySet().iterator();
        while(ite.hasNext()){
            Map.Entry<Integer, Integer> e= ite.next();
            if(e.getValue() == 1){
                if(flag == 0){
                    num1[0] = e.getKey();
                    flag = 1;
                }
                else
                    num2[0] = e.getKey();
            }
        }
    }
}

和为S的两个数字 VS 和为S的连续正数序列

和为S的两个数字

题目描述

输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。

分析

在这里插入图片描述
找到的第一对数字即为所求。
即使有很多对满足要求,相差越大的两个数的乘积越小。(证明略)

Java代码

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
        int head = 0;
        int tail = array.length - 1;
        ArrayList<Integer> res = new ArrayList<Integer>();
        while(head < tail){
            int temp = array[head] + array[tail];
            if(temp < sum)
                head++;
            else if(temp > sum)
                tail--;
            else{
                res.add(array[head]);
                res.add(array[tail]);
                break;
            }
        }
        return res;
    }
}

和为S的连续正数序列

题目描述

输入一个正数S,打印出所有和为S的连续正数序列(至少含有两个数)。例如输入15,由于1+2+3+4+5 = 4+5+6 = 7+8 = 15,所以结果打印除3个连续序列:

1 2 3 4 5
4 5 6
7 8

分析

使用双指针head和tail,相当于一个窗口。由于是一个连续递增序列,所以head<tail,且 s u m = ∑ X = h e a d t a i l X = ( h e a d + t a i l ) ( t a i l − h e a d + 1 ) / 2 sum=\sum_{X=head}^{tail}X=(head+tail)(tail-head+1)/2 sum=X=headtailX=(head+tail)(tailhead+1)/2
假设S=15,具体操作如下:
在这里插入图片描述

Java代码

import java.util.ArrayList;
public class Solution {
    public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
                ArrayList<ArrayList<Integer>> res = new ArrayList<ArrayList<Integer>>();
        int head = 1;
        int tail = 2;
        while(tail <= (sum+1)/2){
            int temp = (head+tail)*(tail-head+1)/2;
            if(temp == sum){
                ArrayList<Integer> list = new ArrayList<Integer>();
                for(int i=head; i<=tail; i++)
                    list.add(i);
                res.add(list);
                head++;
                tail++;
            }
            else if(temp > sum)
                head++;
            else if(temp < sum)
                tail++;
        }
        return res;
    }
}

翻转单词序列 VS 左旋转字符串

翻转单词序列

题目描述

输入一个英语句子,翻转橘子中给单词的顺序,但单词内字符的顺序不变。为简单期间,标点符号和普通字母一样处理。
例如,输入字符串"I am a student.",则输出"student. a am I"。

分析

第一步:翻转整个句子中的所有字符。

“I am a student.” → “.tneduts a ma I”

第二步:翻转每个单词。

“.tneduts a ma I” → “student. a am I”。

即可完成。

Java代码

public class Solution {
    public String ReverseSentence(String str) {
        char[] array = str.toCharArray();
        int len = array.length;
        // 翻转整个句子
        reverse(array, 0, len-1);
        // 翻转每个单词
        int head = 0;
        int tail = 0;
        while(head < len){
            if(array[head]==' '){
                head++;
                tail++;
            }
            else if(tail>=len || array[tail]==' '){
                reverse(array, head, --tail);
                head = ++tail;
            }
            else
                tail++;
        }
        return new String(array);
    }
    public void reverse(char[] arr, int head, int tail){
        while(tail>head){
            swap(arr, head++, tail--);
        }
    }
    public void swap(char[] arr, int i, int j){
        char temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

左旋转字符串

题目描述

字符串的左旋转操作时把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。
例如输入字符串

abcdefg 2

即将字符串左旋转2位,输出

cdefgab

分析

第一步:将abcdefg根据旋转的位数分成两个部分,abcdefg
第二步:将两个部分分别翻转,得到bagfedc
第三步:将整个字符串翻转,即可得到最后的结果cdefgab

Java代码

public class Solution {
    public String LeftRotateString(String str,int n) {
        char[] arr = str.toCharArray();
        int len = arr.length;
        if(n>len)
            return "";
        // 翻转前n个字符
        reverse(arr, 0, n-1);
        // 翻转其他字符
        reverse(arr, n, len-1);
        // 翻转所有字符
        reverse(arr, 0, len-1);
        return new String(arr);
    }

    public void reverse(char[] arr, int head, int tail){
        while(head<tail){
            swap(arr, head++, tail--);
        }
    }

    public void swap(char[] arr, int i, int j){
        char temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值