lintcode (找到出现频率最高,将一个链表转换为一个数组, 查找斐波纳契数列中第 N 个数,链表的中点(不遍历),string转int,升序数组,目标最后位置,尾部的零

给定一个字符串数组ipLines, 每一个元素代表一个IP地址,找到出现频率最高的IP。

样例1:

输入 = ["192.168.1.1","192.118.2.1","192.168.1.1"]
输出  "192.168.1.1"

样例2:

输入 = ["192.168.1.1","192.118.2.1","192.168.1.1","192.118.2.1","192.118.2.1"]
输出 "192.118.2.1"
      public String highestFrequency(String[] ipLines) {
        // Write your code here
        String ans = ipLines[0];
        if(ipLines.length == 0 || ipLines == null){
            return ans;
        }
        Map<String, Integer> count = new HashMap<>();
        int maxFre = 0;
        //遍历
        for (String ipLine : ipLines) {
            //map添加,      getOrDefault : 如果map中含有指定的key,就返回该key对应的value,否则使用该方法的第二个参数作为默认值返回
            count.put(ipLine, count.getOrDefault(ipLine, 0) + 1);

             //  两个数比大小,取大
            maxFre = Math.max(maxFre, count.get(ipLine));
        }
        //为了获取key
        for (String ipLine : ipLines) {
            if (count.get(ipLine) == maxFre) {
                return ipLine;
            }
        }
        return null;
    }

收获:

getOrDefault : 如果map中含有指定的key,就返回该key对应的value,否则使用该方法的第二个参数作为默认值返回

Math.max:静态的工具方法,两个数比大小,取大(有关数字的直接找Math)

 

将一个链表转换为一个数组

样例 1:

输入: 1->2->3->null
输出: [1,2,3]

样例 2:

输入: 3->5->8->null
输出: [3,5,8]
/**
 * Definition for ListNode
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */

public class Solution {
    /**
     * @param head: the head of linked list.
     * @return: An integer list
     */
    public List<Integer> toArrayList(ListNode head) {
        // write your code here
        //创建数组
        ArrayList array=new ArrayList();
        //遍历链表
        while(head!=null){
            //赋值
            array.add(head.val);
            //下一个
            head=head.next;
        }
        return array;
    }
}

 查找斐波纳契数列中第 N 个数。

递归做法

public class Solution {
    /**
     * @param n: an integer
     * @return: an ineger f(n)
     */
    public int fibonacci(int n) {
        // write your code here
        //用递归做
       if(n==1){
           return 0;
       }
       if(n==2){
           return 1;
       }
       return fibonacci(n-1)+fibonacci(n-2);
    }
}

收获:

没想到还能这么做,一直往下递归下去,最终找到0和1.缺点是 会影响性能,反应慢

链表的中点(不要遍历链表)

样例 1:

输入:  1->2->3
输出: 2	
样例解释: 返回中间节点的值

样例 2:

输入:  1->2
输出: 1	
样例解释: 如果长度是偶数,则返回中间偏左的节点的值。	

 

收获:

什么是链表 : 

  • 数据存储在“节点”(Node)中
class Node{
	E e;
	Node next;
}

 

最后一个节点指向nullNode.next = null)。

0(head) -> 1 -> 2 -> 3 -> 4 -> null

 

分为单链表和双向链表
  1.单链表:每个节点包含两部分,一部分存放数据变量的data,另一部分是指向下一节点的next指针
  2.双向链表:除了包含单链表的部分,还增加的pre前一个节点的指针

   优点 : 不需要连续的存储单元,修改链表的复杂度为O(1) (在不考虑查找时) ,  真正的动态,不需要处理固定容量的问题
   缺点 : 无法直接找到指定节点,只能从头节点一步一步寻找复杂度为O(n)

什么是ListNode : 

public class ListNode{
    int val;
    ListNode next;        //链表指向的下一个值的指针
    ListNode(int x){val = x;}   //这个方式赋值
}

什么是指针 : 

什么是快慢指针 : 用一快一慢的两个指针,快指针每次走两步,慢指针每次走一步,那么快指针到达链尾的时候,慢指针就走了中点,返回它就行了!

/**
 * Definition for ListNode
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */

public class Solution {
    /**
     * @param head: the head of linked list.
     * @return: a middle node of the linked list
     */
    public ListNode middleNode(ListNode head) {
        // write your code here
        //如果链表为空,直接返回
          if (head == null || head.next == null) {
            return head;
        }
         //快慢指针,
        ListNode slow =head;
        ListNode fast =head;
        //循环遍历
        while(fast.next!=null && fast.next.next!=null){
            //快慢指针,慢走一步,快走两步
             slow = slow.next;
             fast=fast.next.next;
        }
        return slow;
    }
}

计算链表中有多少个节点.

样例  1:
	输入:  1->3->5->null
	输出: 3
	
	样例解释: 
	返回链表中结点个数,也就是链表的长度.

样例 2:
	输入:  null
	输出: 0
	
	样例解释: 
	空链表长度为0
/**
 * Definition for ListNode
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */

public class Solution {
    /**
     * @param head: the first node of linked list.
     * @return: An integer
     */
    public int countNodes(ListNode head) {
        // write your code here
        //判断空值
         if (head == null) {
            return 0;
        }
        //计数器
        int cound=0;
        //遍历
        while(head!=null){
            //下一个
            head=head.next;
            //计数器+1
            cound++;
        }
        return cound;
    }
}

反转一个只有3位数的整数。

样例 1:

输入: number = 123
输出: 321

样例 2:

输入: number = 900
输出: 9
public class Solution {
    /**
     * @param number: A 3-digit number.
     * @return: Reversed number.
     */
    public int reverseInteger(int number) {
        // write your code here
        //用long是怕反转后的数超过int上限
        long result = 0;
        //遍历  如: 234
        while (number != 0){    //得0 则不再循环
            //4 = 0 * 10 + 234 % 10
            // 43 = 4*10 + 23 % 10
            //432 = 43*10 + 2%10
            result = result * 10 + number % 10;
            //23=234/10
            //2=23/10
            //0=2/10
            number = number / 10;
        }
        //如果超过上限或低于下限则返回0
        if (result > Integer.MAX_VALUE || result < Integer.MIN_VALUE){
            return 0;
        }
        return (int)result;
    }
}

收获 : 

     记得考虑int类型最大值和最小值问题

求二叉树中结点的最大值---存疑

样例1:

输入:
{1,-5,3,1,2,-4,-5}
输出: 3
说明:
这棵树如下所示:
     1
   /   \
 -5     3
 / \   /  \
1   2 -4  -5

样例 2

输入:
{10,-5,2,0,3,-4,-5}
输出: 10
说明:
这棵树如下所示:
     10
   /   \
 -5     2
 / \   /  \
0   3 -4  -5 
/**
 * Definition of TreeNode:
 * public class TreeNode {
 *     public int val;
 *     public TreeNode left, right;
 *     public TreeNode(int val) {
 *         this.val = val;
 *         this.left = this.right = null;
 *     }
 * }
 */
public class Solution {
    /*
     * @param root: the root of tree
     * @return: the max node
     */
    public TreeNode maxNode(TreeNode root) {
        // write your code here
        if(root==null){
            return root;
        }
        TreeNode left = maxNode(root.left);
        TreeNode right=maxNode(root.right);
        return max(root,max(left,right));
    }
    TreeNode max(TreeNode a,TreeNode b){
        if(a==null){
            return b;
        }
        if(b==null){
            return a;
        }
        if(a.val > b.val){
            return a;
        }
        return b;
    }
}

收获:

二叉树:

常用术语:

在链表中找值为 value 的节点,如果没有的话,返回空(null)。

样例 1:

输入:  1->2->3 and value = 3
输出: 最后一个结点

样例 2:

输入:  1->2->3 and value = 4
输出: null
/**
 * Definition for ListNode
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */


public class Solution {
    /*
     * @param head: the head of linked list.
     * @param val: An integer.
     * @return: a linked node or null.
     */
    public ListNode findNode(ListNode head, int val) {
        // write your code here
       while(head!=null){
           if(head.val == val){
               //如果相同就返回数值,
               return head;
           }
           head=head.next;
       }
       //没有相同的返回null
       return null;
    }
}


 

string转int

给一个字符串, 转换为整数. 你可以假设这个字符串是一个有效的整数的字符串形式, 且范围在32位整数之间 (-231 ~ 231 - 1)。

样例

样例  1:
	输入:  "123"
	输出: 123
	
	样例解释: 
	返回对应的数字.

样例 2:
	输入:  "-2"
	输出: -2
	
	样例解释: 
	返回对应的数字,注意负数.

 

public class Solution {
    /**
     * @param str: A string
     * @return: An integer
     */
    public int stringToInteger(String str) {
        // write your code here
            
 // 方案1
         //Integer.parseInt() 返回的是一个int的值。
         //return  Integer.parseInt(str);
 //方案2
         //Integer.valueof()返回的是Integer的对象。
          //  return  Integer.valueOf(str);

 //方案3

        // Write your code here
        int integer = 0;
        int Minus = 0;
        //为了判断是否带负号
        if (str.charAt(0) == '-'){
            Minus = 1;
        }
        
        for (int i = Minus; i < str.length(); i++){
            //这里的-'0', char类型数字-char类型'0',得到int类型数字
            integer = integer * 10 + str.charAt(i)-'0';
        }
        //有负号加上负号
        if (Minus == 1){
            integer = -integer;
        }
        return integer;
        
    }
}

收获:

           //这里的-'0', char类型数字-char类型'0',得到int类型数字
            integer = integer * 10 + str.charAt(i)-'0';

目标最后位置

给一个升序数组,找到 target 最后一次出现的位置,如果没出现过返回 -1

样例 1:

输入:nums = [1,2,2,4,5,5], target = 2
输出:2

样例 2:

输入:nums = [1,2,2,4,5,5], target = 6
输出:-1
public class Solution {
    /**
     * @param nums: An integer array sorted in ascending order
     * @param target: An integer
     * @return: An integer
     */
    public int lastPosition(int[] nums, int target) {
        // write your code here
        //排除空值,长度0,过大,过小
         if(nums == null || nums.length == 0||target < nums[0] || target > nums[nums.length-1])
            {return -1;}

        int start  = 0, end  = nums.length-1;
        // 循环
        while(start < end - 1){
            //二分法
            int mid = start + (end - start) / 2;
            if(nums[mid] > target){
                end = mid - 1;
            }
            else{
                start = mid;
            }
        }
        //end在start上面
         if(nums[end] == target){
             return end;
         }
        if(nums[start] == target){
             return start;
        }
        return -1;
    }
}      
      

 

收获:

二分法查找,捋清楚思路

尾部的零

设计一个算法,计算出n阶乘中尾部零的个数

样例  1:
	输入: 11
	输出: 2
	
	样例解释: 
	11! = 39916800, 结尾的0有2个。

样例 2:
	输入:  5
	输出: 1
	
	样例解释: 
	5! = 120, 结尾的0有1个。

解法思路:

可以将每个数拆分成其素因子的乘积,可以发现,0是由2*5产生的,而5的数量一定小于2的数量,因此5的个数决定了结尾0的个数。

只要计算n的阶乘中,5这个素因子出现多少次即可。

还需要系统性学习算法,野路子终究不行.

/**
* This reference program is provided by @jiuzhang.com
* Copyright is reserved. Please indicate the source for forwarding
*/

class Solution {
    /*
     * param n: As desciption
     * return: An integer, denote the number of trailing zeros in n!
     */
    public long trailingZeros(long n) {
        long sum = 0;
        while (n != 0) {
            sum += n / 5;
            n /= 5;
        }
        return sum;
    }
};

 

收获:

(本题解法转载:https://blog.csdn.net/surp2011/article/details/51168272)

算法1:最朴素
面对此问题,第一反应是直接计算结果:11!=39916800,然后设计程序判断末尾的0的个数,很简单就可以实现。
但是相应的会有很多的问题:
1、计算阶乘的开销
现在只是11的阶乘,都已经很大了,如果是5555550000000的阶乘呢?按照程序的计算结果,末尾会有1388887499996个0,计算开销很值得考虑。
2、溢出
按照上面的介绍,5555550000000的阶乘有1388887499996个0,那么可以推知阶乘的结果会是很大的一个整数,肯定会超出long类型的界限,结果会溢出。这样还要考虑处理溢出问题,又是另一个问题。
3、效率
算法2会涉及到效率问题,会发现即使是算法2也会出现计算时间超出要求的问题,那么更为“朴素”的算法1效率更是可想而知了。
因此,算法1,舍弃。
 

算法2:以5为迭代步数
算法2分析
仔细的考虑问题,会发现末尾出现的0是10或10的倍数相乘的结果,而10其实是5与偶数相乘。也就是,最终结果中末尾出现的0是5、10、15、20、25…自身或与偶数相乘之后的产生的。下面可以分为偶数和5的倍数分析。

首先考虑偶数。
考虑2的幂次项2、4、8…中的2的个数,发现2的幂指数的增长速度远比5的幂指数增长的快,更不用说其他的普通偶数6、12、14…。因此可以认为有足够的偶数与奇数形式的5的倍数相乘产生足够的0。所以我们后面只考虑5的倍数。

接着考虑5的倍数。

1、2、3、4、5、6、7、8、9、10、11...
1
其实1、2、3、4、6、7…都是可以不用考虑的,因此选择以5为迭代步数即可。
首先,这些数字都可以不用进行%5(对5取余数)运算,因此每次循环时可以直接将函数的count变量直接加1。其次,考虑25、125、625…等5的幂次项,因为他们每一个都可以在与偶数相乘之后产生多个0。因此,设置一个循环体,判断是多少幂次项,并将结果加进count。
综上所述,可以编写代码如下:算法2代码

public class Solution {

    /*
     * param n: As desciption return: An integer, denote the number of trailing
     * zeros in n!
     */
    public long trailingZeros(long n) {
        // write your code here
        long count = 0;
        long pwr = 25;
        for (long temp = 5; temp <= n; temp+=5) {
            // for循环内部的temp都是5的倍数,因此首先进行+1操作
            count++;
            pwr = 25;
            // 判断是不是25、125、625...的倍数,并根据每次pwr的变化进行+1操作
            while (temp % pwr == 0) {
                count++;
                pwr *= 5;
            }
        }
        return count;
    }
}


代码很简单,不再解释。
但是效率很差,分析发现代码的时间复杂度实际是O(N/5)~=O(N),达不到要求的O(logN)。
算法2虽然可以解决问题,但考虑执行效率,算法2应该舍弃。
 

算法3:科学思想
反思&对比
这个算法真的是感触很深,对平时很多习以为常的公式、道理有了非常直观的认识,因此对自己的冲击很大,也促进了思考的进步。

提交算法2的代码,发现前面的简单测试都能通过,但是数值5555550000000测试失败。特别是实现了时间复杂度O(logN)的算法3之后,才发现两者时间开销差别真的是很大。

重新分析
1、2、3、4、5、6、7、8、9、10、11、...

1、分析上面的数列可知,每5个数中会出现一个可以产生结果中0的数字。把这些数字抽取出来是:

...、5、...、10、...、15、...、20、...、25、...

这些数字其实是都能满足5*k的数字,是5的倍数。统计一下他们的数量:n1=N/5。比如如果是101,则101之前应该是5,10,15,20,...,95,100共101/5=20个数字满足要求。

整除操作满足上面的数量统计要求。

2、将1中的这些数字化成5*(1、2、3、4、5、...)的形式,内部的1、2、3、4、5、...又满足上面的分析:每5个数字有一个是5的倍数。抽取为:

...、25、...、50、...、75、...、100、...、125、...

而这些数字都是25的倍数(5的2次幂的倍数),自然也都满足5*k的要求。
这些数字是25、50、75、100、125、...=5*(5、10、15、20、25、...)=5*5*(1、2、3、4、5、...),内部的1、2、3、4、5、...又满足上面的分析,因此后续的操作重复上述步骤即可。
统计一下第二次中满足条件的数字数量:n2=N/5/5,101/25=(101/5)/5=4。
因为25、50、75、100、125、...它们都满足相乘后产生至少两个0,在第一次5*k分析中已经统计过一次。对于N=101,是20。因此此处的5*5*k只要统计一次4即可,不需要根据25是5的二次幂统计两次。
后面的125,250,...等乘积为1000的可以为结果贡献3个0的数字,只要在5*5*k的基础上再统计一次n3=((N/5)/5)/5即可。

3、第三次
其实到这里已经不用再写,规律已经很清楚了。对于例子N=101,只要根据规律进行101/125=((101/5)/5)/5=4/5=0,退出统计。因此最终结果是20+4=24。计算结束。

算法3代码

下面编写打码实现上面的思想。

public class Solution {

    /*
     * param n: As desciption return: An integer, denote the number of trailing
     * zeros in n!
     */
    public long trailingZeros(long n) {
        // write your code here
        long count = 0;
        long temp=n/5;
        while (temp!=0) {
            count+=temp;
            temp/=5;
        }
        return count;
    }
}

代码分析:
算法中每次循环均有除以5的操作,也就是每次都会将所要处理的数据量缩小至上一次的1/5,容易推知时间复杂度为O(logN)。

至此,问题解决。

tips

关于测试代码,按照上一篇文章的介绍,如果使用Main函数调用Solution:trailingZeros()函数,在传入参数较小的时候,不会有什么问题,如下:

public class Test{
    public static void main(String args[]){
        Solution s=new Solution();
        long result=s.trailingZeros(11);
        System.out.println(result);
    }
}

因为11不超过int类型的最大长度,所以并不会报错。但是如果是5555550000000,则会报错:

The literal 5555550000000 of type int is out of range 

将数值进行强制类型转换也不行:long inNum=(long)5555550000000;
一种解决方法是使用Scanner直接读取数值。
改进后的代码如下:

public class Test{
    public static void main(String args[]){
        Solution s=new Solution();
        Scanner scanner=new Scanner(System.in);
        long result=s.trailingZeros(scanner.nextLong());
        System.out.println(result);
    }
}

这时输入5555550000000则不会报错。
另外,如果需要的话,可使用System.currentTimeMillis();观察代码执行时间。

小结
从最终的代码来看,问题是挺简单的。之所以折腾这么久都没有切入要害,直接做到真正的时间复杂度为O(logN)的效果,个人觉得是因为从分析题目的时候就没有真正理解O(logN)的真正含义。
类似于二叉搜索树,从根节点开始比较,比根节点小则与左子树比较,比根节点大则与右子树比较,相等或到达叶子节点则退出。如此循环迭代。
每次判断后,下一次可搜索的数据量均为上一次的1/2,如此循环复杂度为O(logN)。

反思
遇到错误和不足就要反思,吸取教训。正视自己的缺点。

下面是个人吐槽时间,吃瓜子的观众可以有序退场了。

应该来讲,本题的最终目的是要做到O(logN)。分析题目的时候从O(logN)着手分析可能会是更好的方法。从科学的、有章可循的理论出发,作为指导思想,结合之前的例子(二叉搜索树),举一反三,解决本问题不是难事。
但是反过来,采用“朴素”方法,依靠个人经验,观察算法规律,然后解决问题。一个不行再去观察思考尝试下一种方法,虽然也是一种解决问题的思路,但如果想要在此基础上做到有章可循的逐步演进,怕是困难得多。
更何况如果观察不出规律呢?

理论&实践
先分析理论然后落实到实践,还是先动手做,再结合/总结升华出理论,值得推敲。

理性思考有助于身体健康,切记切记。与君共勉。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值