剑指offer (JZ31—JZ40)

JZ31 整数中1出现的次数(从1到n整数中1出现的次数)

中等  通过率:35.38%  时间限制:1秒  空间限制:64M

知识点:数学

描述

输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数
例如,1~13中包含1的数字有1、10、11、12、13因此共出现6次
 

示例1

输入:

13

返回值:

6

思路:

我们看到题目要求 “输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数”。其实就是求从1-n这n个数中,每一个数的计数位上出现的1的个数的总和。

所以我们只需要知道在某个计数位上面1出现次数的计算方法即可求解这道题目,下面我们分为几种情况讨论:

1、当前位上的值等于0

image-20210619103136978

我们可以看到当前位为百位,即base = 100;当前位上的数为cur = 0,此时为什么需要借位呢?

因为当cur为0的时候,后面的b即使取0-36的任何一个数,cur都不可能出现1,永远都是0。

所以需要向前借位,借位后cur才能有为1的情况,当cur借到位的时候,将cur变为1,此时我们就可以去计算1出现的个数了。

此时a的取值范围为 0-513,然后b的范围为0-99。0-513这个好理解,因为被cur借去了一位,但是b不是刚开始只能取0-36吗?为什么就能够变成0-99呢?我们看到,当cur向前借位的时候,cur是可以取0-9任意一个数的,因为我们就是想让cur为1,所以cur这一位完全可以提供给后面的借位,所以b的取值可以为 0-99。

好了,上面说了那么多,此时这种情况下,1出现的次数怎么算呢?

我们可以转化为这么想,当cur为1的时候,最多能够生成多少个cur为1的数呢?不就是cur前面能变换的个数乘上后面能变换的个数吗?

所以n = 514036的时候,cur为1出现的次数 res = a * base

2、当前位上的值等于1时

image-20210619110657754

我们可以从上图很清楚的看到,此时cur=1,并且当a为514的时候,b只能取0-36,此时 res1 = 1 * (b+1)

当a取值 0-519 的时候,b就能够取 0-99 。此时 res2 = a * base

所以总的就为 res = res1 + res2

3、当前位上的值大于1时

image-20210619111157276

我们可以得到a和b的取值范围,至于b为什么又是0-99而不是0-36,解释跟上面一样。

所以 res = (a+1) * base(tips : 0-514 为 a+1 个数)

因此,我们可以按照上面的思路书写代码啦~

public class Solution {
    public int NumberOf1Between1AndN_Solution(int n) {
        int base = 1;//从个人数开始
        int res = 0;//总的出现1的次数现在为0
        while(base<=n){
            //求出当前位,高位,地位
            int cur = n/base%10;
            int a = n/base/10;
            int b = n%base;
            //分三类情况讨论,当前位1,0,>1
            if(cur==0){
                res += a*base;//高位a种变换,低位base种变换
            }
            else if(cur==1){
                res += a*base + b+1;
            }
            else{
                res += (a+1)*base;
            }
            base *= 10;
        }
        return res;
    }
}

JZ32 把数组排成最小的数

较难  通过率:29.91%  时间限制:1秒  空间限制:64M

知识点:数组

描述

输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。

示例1

输入:

[3,32,321]

返回值:

"321323"

思路:暴力递归深度遍历数组,从所有可能组合中找到最大值。

import java.util.ArrayList;

public class Solution {
    
    String max="~";
    
    public void DFS(int[] nums, String num, int[] mark, int cnt){
        if(cnt==nums.length){
            if(num.compareTo(max)<0) max = num;
            return;
        }
        for(int i=0; i<nums.length; i++){
            if(mark[i]==1) continue;
            mark[i]=1;
            DFS(nums, num+String.valueOf(nums[i]), mark, cnt+1);
            mark[i] = 0;
        }
    }
    
    public String PrintMinNumber(int [] numbers) {
        int[] mark = new int[numbers.length];
        DFS(numbers, "", mark, 0);
        return max;
    }
}

JZ33 丑数

较难  通过率:23.01%  时间限制:1秒  空间限制:64M

知识点:数学二分

描述

把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。

示例1

输入:

7

返回值:

8

思路:

我们先看到题目,把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。

有了上面的定义我们就可以知道,丑数的形式就是2^x3^y5^z
所以我们可以定义一个数组res,存储第n个丑数。
因为我们要将丑数按从小到大的顺序排序,所以我们就得将对应的丑数放在对应的下标位置,小的放前面。
因为最小的丑数就是1,所以我们初始化res[0]=1,那么接下来的一个丑数是什么呢?我们自己知道是2。
但是我们能不能有一个格式,去将得到接下来的丑数是谁呢?
这个时候上面我们的出来的丑数的格式就起作用了,丑数的形式无非就是这样2^x3^y5^z

看代码就清楚了

public class Solution {
    public int GetUglyNumber_Solution(int index) {
        if(index<1) return 0;
        int x = 0;//将与2相乘的丑数位置
        int y = 0;//将与3相乘的丑数位置
        int z = 0;//将与5相乘的丑数位置
        int[] res = new int[index+1];
        res[0] = 1;
        for(int i=1; i<index; i++){
            
            int a = res[x]*2;
            int b = res[y]*3;
            int c = res[z]*5;
            //6 6 10这种清楚时,x,y都要向后移,所以下面的判断要有等于到,不能else if
            if(a<=b && a<=c){
                res[i] = a;
                x++;//下次2与x位置的丑数相乘
            }
            if(b<=a && b<=c){
                res[i] = b;
                y++;//下次3与y位置的丑数相乘
            }
            if(c<=a && c<=b){
                res[i] = c;
                z++;//下次5与z位置的丑数相乘
            }
//             System.out.println(res[i]);
        }
        return res[index-1];
    }
}

JZ34 第一个只出现一次的字符

简单  通过率:30.72%  时间限制:1秒  空间限制:64M

知识点:字符串

描述

在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).(从0开始计数)

示例1

输入:

"google"

返回值:

4

思路:遍历字符,统计就行,遇到在前面只出现一次的字符去找字符的后面是否有相同的,没有就是答案。

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

public class Solution {
    public int FirstNotRepeatingChar(String str) {
        int[] count = new int[150];
        for(int i=0; i<str.length(); i++){
//             boolean flag = false;
            if(count[str.charAt(i)]!=0){
                continue;
            }
            for(int j=i+1; j<str.length(); j++){
                if(str.charAt(i)==str.charAt(j)){
                    count[str.charAt(i)] = 1;
//                     flag = true;
                    break;
                }
            }
            if(count[str.charAt(i)]==0) return i;
        }
        return -1;
    }
}

JZ35 数组中的逆序对

中等  通过率:16.52%  时间限制:3秒  空间限制:64M

知识点:数组

描述

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007


对于50\%50%的数据,size\leq 10^4size≤104
对于100\%100%的数据,size\leq 10^5size≤105
 

输入描述:

题目保证输入的数组中没有的相同的数字

示例1

输入:

[1,2,3,4,5,6,7,0]

返回值:

7

思路:

了解归并排序的过程。

如果两个区间为[4, 3] 和[1, 2]
那么逆序数为(4,1),(4,2),(3,1),(3,2),同样的如果区间变为有序,比如[3,4] 和 [1,2]的结果是一样的,也就是说区间有序和无序结果是一样的。
但是如果区间有序会有什么好处吗?当然,如果区间有序,比如[3,4] 和 [1,2]
如果3 > 1, 显然3后面的所有数都是大于1, 这里为 4 > 1, 明白其中的奥秘了吧。所以我们可以在合并的时候利用这个规则。

手写实现一个归并排序就行,归并中,将两半区间有序的数组合并中,发现前一半中有不小于后一半的,那么就是一半数组大小的逆序数。

public class Solution {
    public int InversePairs(int [] array) {
        if(array==null) return 0;
        return mergeSort(array,0,array.length-1);
    }
    
    public int merge(int[] array, int left, int mid, int right){//left,mid,right都是索引
        int i = left;
        int j = mid+1;
        int[] newarray = new int[right-left+1];
        int k = 0;
        int count = 0;
        while(i<=mid && j<=right){
            if(array[i]<=array[j]){
                newarray[k++] = array[i++];
            }
            else{//以后面数组为准
                newarray[k++] = array[j++];
                count += (mid-i+1);//i位置的元素移到后半节的距离。这些都是逆序的
                count %= 1000000007;
            }
        }
        while(i<=mid){
            newarray[k++] = array[i++];
        }
        while(j<=right){
            newarray[k++] = array[j++];
        }
        k=0;
        for(i=left; i<=right; i++){
            array[i] = newarray[k++];
        }
        return count;
    }
    
    public int mergeSort(int[] array, int left, int right){
        if(left>=right){
            return 0;
        }
        int mid = left + ((right-left)>>1);//求最中间的索引
        int leftsum = mergeSort(array, left, mid);
        int rightsum = mergeSort(array, mid+1, right);
        int count = merge(array, left, mid, right);
        return (leftsum+rightsum+count)%1000000007;
    }
    
}

JZ36 两个链表的第一个公共结点

简单  通过率:35.97%  时间限制:1秒  空间限制:64M

知识点:链表

描述

输入两个无环的单链表,找出它们的第一个公共结点。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)

示例1

输入:

{1,2,3},{4,5},{6,7}

返回值:

{6,7}

说明:

第一个参数{1,2,3}代表是第一个链表非公共部分,第二个参数{4,5}代表是第二个链表非公共部分,最后的{6,7}表示的是2个链表的公共部分
这3个参数最后在后台会组装成为2个两个无环的单链表,且是有公共节点的     

示例2

输入:

{1},{2,3},{}

返回值:

{}

说明:

2个链表没有公共节点 ,返回null,后台打印{}  

思路:

方法:双指针法

假如例子如下:

图片说明


显然第一个公共结点为8,但是链表A头结点到8的长度为2,链表B头结点到8的长度为3,显然不好办?
如果我们能够制造一种理想情况,如下:

图片说明


这里先假设链表A头结点与结点8的长度 与 链表B头结点与结点8的长度相等,那么就可以用双指针。

  1. 初始化:指针ta指向链表A头结点,指针tb指向链表B头结点
  2. 如果ta == tb, 说明找到了第一个公共的头结点,直接返回即可。
  3. 否则,ta != tb,则++ta,++tb

所以现在的问题就变成,如何让本来长度不相等的变为相等的?
假设链表A长度为a, 链表B的长度为b,此时a != b
但是,a+b == b+a
因此,可以让a+b作为链表A的新长度,b+a作为链表B的新长度。
如图:

图片说明


这样,长度就一致了,可以用上述的双指针解法了。

时间复杂度:O(m+n), m,n分别为链表A,B的长度,最坏情况下,公共结点为最后一个,需要遍历m+n个结点
空间复杂度:O(1)

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

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
         if(pHead1==null || pHead2==null){
             return null;
         }
         ListNode temp1 = pHead1;
         ListNode temp2 = pHead2;
         while(pHead1!=pHead2){
             if(pHead1==null) pHead1 = temp2;
             else pHead1 = pHead1.next;
             if(pHead2==null) pHead2 = temp1;
             else pHead2 = pHead2.next;
             
         }
        return pHead1;
    }
}

JZ37 数字在升序数组中出现的次数

中等  通过率:32.17%  时间限制:1秒  空间限制:64M

知识点:数组

描述

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

示例1

输入:

[1,2,3,3,3,3,4,5],3

返回值:

4

思路:打表,见代码

public class Solution {
    public int GetNumberOfK(int [] array , int k) {
       int[] hash = new int[11111111];
        for(int i=0; i<array.length; i++){
            hash[array[i]]++;
        }
        return hash[k];
    }
}

JZ38 二叉树的深度

简单  通过率:48.47%  时间限制:1秒  空间限制:64M

知识点:

描述

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

示例1

输入:

{1,2,3,4,5,#,6,#,#,7}

返回值:

4

思路:递归深度搜索树

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

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

    }

}
*/
public class Solution {
    public int max = 0;
    public void DFS(TreeNode root, int count){
        if(root==null){
            if(count>max) max = count;
            return;
        }
        count++;
        DFS(root.left, count);
        DFS(root.right, count);
        return;
    }
    
    public int TreeDepth(TreeNode root) {
        DFS(root,0);
        return max;
    }
}

JZ39 平衡二叉树

简单  通过率:37.87%  时间限制:1秒  空间限制:64M

知识点:dfs

描述

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

在这里,我们只需要考虑其平衡性,不需要考虑其是不是排序二叉树

平衡二叉树(Balanced Binary Tree),具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

注:我们约定空树是平衡二叉树。

示例1

输入:

{1,2,3,4,5,6,7}

返回值:

true

思路:递归遍历整棵树,发现左右字数深度差大于1则不是平衡二叉树。

import java.util.*;
public class Solution {
    /*
    从下往上算左右子树的深度。每个节点只访问一次。
    如果不是二叉平衡树,则直接返回-1结束
    如果是二叉平衡树,最后返回的是二叉平衡树的最大深度
    (从上往下算深度时,需要重复访问子树中的节点,浪费很严重。)
    */
    public int judeDfs(TreeNode root){ //返回该节点的层数
        if(root==null){
            return 0;
        }
        int leftCnt = judeDfs(root.left);
        if(leftCnt==-1) return -1;
        int rightCnt = judeDfs(root.right);
        if(rightCnt==-1) return -1;
        if(Math.abs(leftCnt-rightCnt)>1){
            return -1;
        }
        else return 1+Math.max(leftCnt, rightCnt); // 左右子树中最深的深度.当前结点要加1
    }
    
    public boolean IsBalanced_Solution(TreeNode root) {
        if(root==null) return true;
        return judeDfs(root)!=-1;
    }
}

JZ40 数组中只出现一次的两个数字

中等  通过率:54.89%  时间限制:1秒  空间限制:256M

知识点:哈希

描述

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

示例1

输入:

[1,4,1,6]

返回值:

[4,6]

说明:

返回的结果中较小的数排在前面 

思路:hash

import java.util.*;


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param array int整型一维数组 
     * @return int整型一维数组
     */
    public int[] FindNumsAppearOnce (int[] array) {
        // write code here
        int[] hash = new int[11111111];
        
        for(int i=0; i<array.length; i++){
            hash[array[i]] += 1;
        }
        List<Integer> list = new ArrayList();
        for(int i=0; i<array.length; i++){
            if(hash[array[i]]==1) list.add(array[i]);
        }
        int[] ans = new int[list.size()];
        for(int i=0; i<ans.length; i++){
            ans[i] = list.get(i);
        }
        return ans;
    }
}

主页有后续更新

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值