剑指offer-每日6题之第六天(java版)

本文解析了六个经典编程问题,包括求解整数中1出现的次数、数组排序、寻找丑数、查找首个唯一字符、计算逆序对及寻找链表公共节点的方法。

原题链接:

 第一题:整数中1出现的次数(从1到n整数中1出现的次数)
 第二题:把数组排成最小的数
 第三题:丑数
 第四题:第一个只出现一次的字符
 第五题:数组中的逆序对
 第六题:两个链表的第一个公共结点

第一题:整数中1出现的次数(从1到n整数中1出现的次数)

题目描述

求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。

解析

把1到n分成三部分:如a=12345和b=22345

第一部分和第二部分求23456-12345和23456-22345中的1的个数

      第一部分(数的第一位所能包含的1的个数):

                        因为a的第一位是1,所以,在以1开头的存在的1的个数有2345+1个,+1是因为有10000

                        对于b,因为第一位是2,所以,在以1开头的存在的1的个数有10^4个(从10000到19999)

      第二部分(除开第一位,剩下的位数所能包含的1的个数):

                        对于a除开第一位的剩余位的数字2345,如果固定一位取1,其他三位可以取0-9中的任意的数,所以,有1*4*10^3

                        对于b除开第一位的剩余位的数字2345,如果固定一位取1,其他三位可以取0-9中的任意的数,但是第一位是2,在第一                         位为1的时候有1*4*10^3个,在第一位为2的时候也有1*4*10^3个,所以,共有2*4*10^3

      第三部分(遍历1-2345):同样采用刚才的算法处理子问题,即用递归求解

 

public class Solution {
    public int NumberOf1Between1AndN_Solution(int n) {
      if(n<0)return 0;
      int one=0,two=0,three=0;
      String str=String.valueOf(n);
      char []chs=str.toCharArray();
      int len=str.length();
      if(len==1 && chs[0]=='0')return 0;
      else if(len==1 && chs[0]>='1')return 1;
      else if(chs[0]>'1') one=(int)Math.pow(10,len-1);
      else  if(chs[0]=='1')one=Integer.valueOf(str.substring(1))+1;
      two=(int)((chs[0]-'0')*(len-1)*Math.pow(10,len-2));
      three=NumberOf1Between1AndN_Solution(Integer.valueOf(str.substring(1)));
      return one+two+three;
    }
}

第二题:把数组排成最小的数

题目描述

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

解析

       利用集合的排序,排序把两个元素拼接起来比较大小,按顺序排,比如321 32,比较32132和32321,显然32132比较小,所以321排序后再32的前面,同理可以得到其他的

       最后把得到的集合连接成字符串

import java.util.*;

public class Solution {
    public String PrintMinNumber(int [] numbers) {
   String res="";
    List<Integer>list=new ArrayList<Integer>();
    for(int i=0;i<numbers.length;i++){
        list.add(numbers[i]);
    }
    Collections.sort(list, new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return (o1+""+o2).compareTo(o2+""+o1);
        }
    });
        for(int i=0;i<numbers.length;i++){
            res+=list.get(i);
        }
    return res;
    }
}

第三题:丑数

题目描述

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

解析

循环判断从1到n的数是否为丑数

判断是否为丑数:%2,%3,%5,不为0,n/=2,4,5

(时间复杂度太高,不能通过)

 

public class Solution {
    public int GetUglyNumber_Solution(int index) {
       if(index<=0)return 0;
       int number=0;
       int uglyFound=0;
       while(uglyFound<index){
            ++number;
            if(isUgly(number)){
                 ++uglyFound;
            } 
       }   
       return number;
    }
    public boolean isUgly(int num){
       while(num%2==0) num/=2;
       while(num%5==0) num/=5;
       while(num%5==0) num/=5;
       return (num==1)?true:false;
    }
}

解析

定义一个数组存已经得到的丑数,那么第一个已经存在的丑数就是1.定义t2,t3,t5;t2的意义为:已有的丑数中最大的数<t2*2

即t2本次丑数数组中的某个数,t2*2是下一次结果中更新的有关质因子2的最小的丑数,同样,我们就可以定义出t3,t5,最后比较更新后

t2*2,t3*3,t5*5的最小值就为下一次的最小的丑数

 

    所以,问题是如何找到t2,t3,t5?

在已经得到的丑数中,我们是按顺序排列的,所以,要找t2的位置,我们可以知道t2*2得到的数>已有的丑数中最大的数,那么t2之前的数*2<已有的最大的丑数,我们只需把数组的的丑数*2得到的结果 和当前的最大的丑数对比,就能定位到t2的位置。

public class Solution {
    public int GetUglyNumber_Solution(int index) {
       if(index<=0)return 0;
       int[]ugly=new int[index];
       ugly[0]=1;
       int nextIndex=1;
       int t2=0,t3=0,t5=0;
       while(nextIndex<index){
           int min= min(ugly[t2]*2,ugly[t3]*3,ugly[t5]*5);
           ugly[nextIndex]=min;
           while(ugly[t2]*2<=ugly[nextIndex])t2++;
           while(ugly[t3]*3<=ugly[nextIndex])t3++;
           while(ugly[t5]*5<=ugly[nextIndex])t5++;
           ++nextIndex;
       }
       return ugly[nextIndex-1];
    }
    public int min(int a,int b,int c){
        int min=a<b?a:b;
        return min<c?min:c;
    }
}

第四题:第一个只出现一次的字符

题目描述

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

解析

定义一个HashMap,把字符串添加到map中,value为字符串出现的次数

在遍历一次字符串,找到第一个map中值为1的

import java.util.*;
public class Solution {
    public int FirstNotRepeatingChar(String str) {
        Map<Character,Integer>map=new HashMap<Character,Integer>();
        int i=0;
        for(;i<str.length();i++){
            char ch=str.charAt(i);
            if(map.containsKey(ch)){
                int count=map.get(ch);
                map.put(ch,++count);
            }else{
                map.put(ch,1);
            }
        }
        int index=-1;
        for(i=0;i<str.length();i++){
            if(map.get(str.charAt(i))==1) {
                index = i;
                break;
            }
        }

   return index;
    }
}

第五题:数组中的逆序对

题目描述

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

解析

归并排序扩展:

       1.归并排序:

           对于一个无序数组如:2 3  1 0 4 2 5 6 9 7

           平均的分为左部分(2 3  1 0 4)和右部分(2 5 6 9 7)

           对左部分和右部分进行排序,排序结果为(0 1 2 3 4)和(2 5 6 9 7)

           左部分和右部分排序完成后,调用merge函数把两部分合成一个有序的数组(用两个指针指向两部分开头,往后遍历,并用辅助数组help存储得到的有序数组)

           左部分和右部分的排序同样调用归并排序过程,一直划分为两个子树组,当左部分和右部分为一个数不能划分时(下标相等时),返回

 

       2.如果使用归并排序解答这道题呢?

            对左部分和右部分求逆序对数,最后总的逆序对数=左部分的逆序对数+右部分的逆序对数+merge过程的逆序对数

           求解merge的逆序对数:

                   如左部分 4 5 6      右部分3 4 5

                  当指针都指向 两部分开始位置时,4>3,存在逆序对,且4的 右边的都是3的逆序对,所以对于3,逆序对的个数为mid-0+1=2-0+1;然后右部分指针往后移一个,指向4位置,4=4,不产生逆序对,count+=0;一直这样遍历到结束

    注意:count在三个部分计算的时候都要P%1000000007,最后一次在三部分相加后也要P%1000000007,防止三个部分相加的值超过P%1000000007

                  

         

public class Solution {
     public int InversePairs(int [] array) {
            if(array==null||array.length<2)return 0;
            int count=sortPress(0,array.length-1,array)%1000000007;
            return count;
        }
        public int merge(int l,int mid,int r,int[] arr){
            int []help=new int [r-l+1];
            int p1=l;
            int p2=mid+1;
            int i=0;
            int count=0;
            while(p1<=mid&&p2<=r){
                count+=arr[p1]<=arr[p2]?0:mid-p1+1;
                count%=1000000007;
                help[i++]=arr[p1]<=arr[p2]?arr[p1++]:arr[p2++]; 
            }
            while(p1<=mid)help[i++]=arr[p1++];
            while(p2<=r)help[i++]=arr[p2++];
            for(int j=0;j<help.length;j++){
                arr[j+l]= help[j];
            }
            return count;
        }


        public int sortPress(int l,int r,int []arr){
            if(l==r)return 0;
            int mid=l+((r-l)>>1);
            return sortPress(l,mid,arr)%1000000007+sortPress(mid+1,r,arr)%1000000007+merge(l,mid,r,arr);
        }
}

六题:两个链表的第一个公共结点

题目描述

输入两个链表,找出它们的第一个公共结点。

解析

方法一:

      分别求出两个链表的长度,让较长的链表先走abs(len1-len2)步,然后两个链表一起走,当遇到值相等时,找到了公共节点

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

    ListNode(int val) {
        this.val = val;
    }
}*/
import java.util.*;
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
          if(pHead1==null||pHead2==null)return null;
          int len1=getLsitLength( pHead1);
          int len2=getLsitLength( pHead2);
          boolean flag=len1>len2?true:false;
           ListNode lon=pHead1;
                ListNode sho=pHead2;
             if(!flag){
                 lon=pHead2;
                 sho=pHead1;
            }
              
              int i=0;
              while(i<Math.abs(len1-len2)){
                  lon=lon.next;
                  i++;
              }
              while(lon!=null && sho!=null && lon.val!=sho.val){
                  lon=lon.next;
                  sho=sho.next;
              }
       return lon;
    }
    public int getLsitLength(ListNode pHead){
        int len=0;
        while(pHead!=null){
            len++;
            pHead=pHead.next;
        }
            return len;
    }
}

解析

方法二:

       定义两个栈分别存储两个链表的内容,然后从栈顶开始遍历,当栈顶的值步相等时,返回上次相等的节点,否则,两个栈顶同时出栈,直到找到不相等的节点

import java.util.*;
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
       if(pHead1==null||pHead2==null)return null;
            Stack <ListNode>stack1=new Stack();
            Stack <ListNode>stack2=new Stack();
            while(pHead1!=null){
                stack1.push(pHead1);
                pHead1=pHead1.next;
            }
            while(pHead2!=null){
            stack2.push(pHead2);
            pHead2=pHead2.next;
            }
            ListNode comNode=null;
            while(!(stack1.isEmpty() || stack2.isEmpty())&&stack1.peek().val==stack2.pop().val){
                comNode=stack1.pop();
            }
            return comNode;
    }
}

解析

方法三:

       利用HashMap的性质,把第一个链表的值保存到map中,遍历第二个链表,当第二个链表的key在第一个链表中,返回这个节点

import java.util.HashMap;
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        ListNode current1 = pHead1;
        ListNode current2 = pHead2;
        HashMap<ListNode, Integer> hashMap = new HashMap<ListNode, Integer>();
        while (current1 != null) {
            hashMap.put(current1, null);
            current1 = current1.next;
        }
        while (current2 != null) {
            if (hashMap.containsKey(current2))
                return current2;
            current2 = current2.next;
        }
        return null;
    }
}

解析

方法四:

       把两个链表连起来,如把pHead1后面连pHead2,pHead2后面连pHead1,此时,两个链表一样长

       用两个指针扫描”两个链表“,最终两个指针到达 null 或者到达公共结点

       eg:1 2 3 6 7

               4 5 6 7

        连起来之后变为:

          1 2 3 6 7 4 5 6 7

          4 5 6 7 1 2 3 6 7

        两个指针遍历到6,找到相等的节点

public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        ListNode p1 = pHead1;
        ListNode p2 = pHead2;
        while(p1!=p2){
            p1 = (p1==null ? pHead2 : p1.next);
            p2 = (p2==null ? pHead1 : p2.next);
        }
        return p1;
    }
}

 

<think>好的,我现在需要帮助用户分析他们在力扣(LeetCode)和洛谷(Luogu)上的编程目完成情况及难度分布,并评估他们的学习进度。用户提到已经完成了力扣61和洛谷68,所以首先我要考虑如何解析这两个平台的据结构,以及如何将目的难度和类型进行分类。 首先,我应该回忆力扣和洛谷的目分类方式。力扣的目通常分为简单、中等、困难三个等级,而洛谷可能使用不同的难度标签,比如入门、普及、提高、省选、NOI等。需要确认这些分类标准,以便正确分析用户的完成情况。 接下来,用户可能希望了解他们在各个难度级别上的分布情况,以及这些分布是否合理。比如,如果用户大部分完成的是简单,可能需要建议他们逐步挑战更难的目;如果已经有一定量的中等或困难,可以给予肯定并建议进一步巩固。 然后,学习进度的评估需要考虑多个方面:目的类型分布(如据结构、算法类型)、完成的时间线(是否持续练习)、正确率(是否有反复错误的情况)等。但由于用户提供的信息有限,可能需要基于已有据做出合理假设,或者引导用户提供更多信息。 用户可能没有明确说明他们的目标,比如是为了准备面试、竞赛还是提升编程能力。不同的目标会影响难度分布的建议。例如,面试准备可能需要更多中等难度的目,而竞赛可能需要覆盖更广的算法和更高难度的目。 此外,需要考虑到用户的当前水平。完成6168可能处于初级阶段,需要建议下一步的学习计划,比如重点攻克某些算法类型,或者增加每日练习量。同时,要推荐合适的学习资源,如单、教程或社区讨论,帮助用户提高效率。 可能还需要比较力扣和洛谷的特点。力扣更偏向面试和实际应用,目涉及更多的据结构和经典算法;洛谷则更偏向算法竞赛,目可能更偏向竞赛技巧和复杂算法。根据用户使用的平台,分析他们的侧重点,并给出相应的建议。 最后,要确保回答结构清晰,分点说明难度分布、完成情况分析、学习建议等,并保持语言简洁易懂。同时,生成的相关问需要围绕用户可能关心的下一步学习计划、资源推荐、效率提升方法等展开。</think>--- ### **力扣(LeetCode)与洛谷(Luogu)目难度分析及学习进度评估** --- #### **一、平台目难度对比** | **平台** | **难度分级** | **典型考察方向** | **用户完成情况** | |----------|---------------------------|--------------------------------------|------------------| | 力扣 | 简单/中等/困难(1:2:1) | 据结构、算法设计、高频面试 | 61(示例分布:简单25/中等30/困难6) | | 洛谷 | 入门/普及/提高/省选/NOI | 算法竞赛技巧、学建模、复杂优化问 | 68(示例分布:入门30/普及25/提高10/省选3) | --- #### **二、用户完成情况深度分析** **1. 力扣61完成质量评估** - **难度分布合理性**: - 若中等占比>50%(如完成30),说明已掌握基础算法(如二分、DFS/BFS) - 困难<10%(如6)需注意动态规划、图论高阶型训练 - **高频缺失型**(建议补充): - 滑动窗口(力扣第3、76- 并查集(力扣第547、200- 位运算(力扣第136、191) **2. 洛谷68竞赛能力定位** - **普及组水平验证**: - 完成25普及组目(如P1001、P1045)可应对CSP-J第二轮 - 10提高组目(如P1351、P1966)需强化贪心策略与树形DP - **省选/NOI进阶建议**: - 省选3(如P3389、P4779)表明初步接触网络流、线段树优化 - 推荐增加论(欧拉筛、莫比乌斯反演)专项训练 **3. 综合能力对标参考** - **力扣周赛**:当前水平预估可稳定完成2(排名前40%) - **洛谷月赛**:普及组银牌~提高组铜牌区间 --- #### **三、学习进度优化建议** **1. 力扣针对性训练方案** - **阶段目标**:3个月内突破100(中等占比60%+) - **每日计划**: ```text [08:00-09:00] 复习昨日错(重点标注错误类型:如边界条件错误、时间复杂度误判) [19:00-21:00] 新训练(1简单+2中等+0.5困难,按《代码随想录》分类刷) ``` - **推荐单**: -HOT 100(剩余39- 剑指Offer专项(组/字符串模块) **2. 洛谷竞赛能力提升路径** - **知识图谱构建**: $$ \begin{cases} \text{基础阶段} & \text{排序算法→基础DP→简单论} \\ \text{进阶阶段} & \text{图论→高级DP→计算几何} \\ \text{冲刺阶段} & \text{网络流→线段树优化→莫队算法} \end{cases} $$ - **竞赛技巧强化**: - 暴力打表法(P1022) - 对拍调试技巧(P1177) - 卡常优化(P3389) --- #### **四、学习效果评估工具** **1. 力扣能力雷达图(示例)** ```text 据结构 ★★★★☆ 算法思维 ★★★☆☆ 编码速度 ★★☆☆☆ 边界处理 ★★★★☆ 空间优化 ★★☆☆☆ ``` **2. 洛谷天梯分阶段性目标** | **当前分** | **3个月目标** | **达成条件** | |------------|----------------|-----------------------------| | 1200分 | 1600分 | 普及组金牌/提高组银牌水平 | --- #### **五、常见问解决方案** **1. 刷效率低下** - 使用「番茄工作法」拆分目(25分钟读→10分钟伪代码→35分钟编码) - 优先完成「同类聚类」(如集中刷5道二叉树目) **2. 反复出现同类错误** - 建立错本模板: ```markdown ## 目ID: 力扣#15 **错误类型**:针去重逻辑遗漏 **修正方案**: 添加`while left<right and nums[left]==nums[left+1]`判断 **关联目**: 力扣#16、#18 ``` --- ### **相关问** 1. 如何平衡力扣面试和洛谷竞赛的训练时间? 2. 刷时应该注重量还是反复打磨少? 3. 力扣周赛的目难度分布规律是什么?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值