徒手挖地球一周目

徒手挖地球一周目

从这篇博文(2019年12月3日12:40:24)正式开始数据结构和算法学习,上一次学习这块儿已经是一年前学校的DS课程了。现在也已经遗忘了百分之九十九了,已然落后于很多坚持训练同学,但是俗话说得好:“积沙成塔,积木成林”,心动不如行动,就依照着LeetCode题库开展“徒手挖穿地球”计划。循序渐进、由浅及深,前面简单部分快速训练,难点知识就放慢脚步系统学习。

NO.1 两数之和 简单

QMw3c9.png

思路一:暴力法 看到题,最先想到的思路就是暴力解法,直接两层for循环遍历:

public int[] twoSum(int[] nums,int target){
    for (int i=0;i<nums.length;i++){
        for (int j=i+1;j<nums.length;j++){
            if (nums[i]+nums[j]==target) {
                return new int[]{i,j};
            }
        }
    }
    throw new IllegalArgumentException("no result!");
}

时间复杂度:O(n^2)

思路二:哈希表法 通过一个哈希表来空间换时间:1.遍历nums数组,判断每个元素和目标值的差temp是否在哈希表中。2.如果在就返回当前遍历元素的下标和哈希表中temp这个key对应的value。3.如果不在就将当前遍历元素作为key、当前遍历元素下标作为value存入哈希表。

public int[] twoSum(int[] nums,int target){
        Map<Integer,Integer> map =new HashMap<Integer,Integer>();

        for (int i=0;i<nums.length;i++){
            int temp=target-nums[i];
//          所需要的temp是否在map中,如果在就返回map中temp值对应的value(即temp值对应的下标)和i。
            if (map.containsKey(temp)){
                return new int[]{map.get(temp),i};
            }
//          temp如果不在map中,就将nums[i]作为key、下标i作为value放入map中
            map.put(nums[i],i);
        }
        throw new IndexOutOfBoundsException("no twoSum result!");
    }

时间复杂度:O(n)

NO.2 两数相加 中等

QMw8XR.png

思路一:转换法 1.将两个链表先转化成int或long类型数值x和y。2.x和y相加后的值再转换成链表。

缺点:当参数中两个链表足够长时,得到的结果很有可能会超出int或long类型的范围发生溢出。

可以将x和y用BigDecimal类型来存储尽可能避免发生溢出,需要注意的是题目中链表都是逆序的

String s1="";
        String s2="";
        ListNode q=l1,p=l2;
//        将两个链表转化为字符串,逆序的链表转换成正序的字符串
        while (q!=null){
            s1=q.val+s1;
            q=q.next;
        }
        while (p!=null){
            s2=p.val+s2;
            p=p.next;
        }
        BigDecimal x=new BigDecimal(s1);
        BigDecimal y=new BigDecimal(s2);
        BigDecimal z=x.add(y);
//        将结果转换成链表,注意此时z是正序的,需要转换成逆序的链表
        char[] chars=z.toString().toCharArray();
        ListNode result=new ListNode(Integer.parseInt(String.valueOf(chars[chars.length-1])));
        ListNode t=result;
//        因为需要链表是逆序的,所以将正序的chars从后行前转换
        for (int i=chars.length-2;i>=0;i--){
            ListNode temp=new ListNode(Integer.parseInt(String.valueOf(chars[i])));
            t.next=temp;
            t=temp;
        }
        return result;

思路二:初等数学法 1.因为链表本身就是逆序的,所以从后向前按位依次加。2.用一个int变量carry来记录前一位相加后得到的进位。3.如果一个链表已经遍历完毕,在后续的按位相加时,该链表的节点值就是0。4.每次按位相加之后更新进位值carry,并将进位之后的数值加入结果链表。5.两个链表遍历相加结束之后,需要再次判断进位置,防止遗漏最高位的进位。

需要注意的是每次按位相加时,不要忘记加上进位值carry。

public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
//        用哑节点来简化代码,如果没有使用哑节点就需要额外的代码来初始化表头的值
        ListNode dummyHead=new ListNode(0);
        ListNode q=l1,p=l2,curr=dummyHead;
//        进位标志carry
        int carry=0;
        while (q!=null||p!=null){
//          获取节点值,如果节点为空,值就为0
            int x=q==null?0:q.val;
            int y=p==null?0:p.val;
//          两个节点值和进位相加
            int sum=x+y+carry;
//          获取相加之后的进位值
            carry=sum/10;
//          将相加后结果加入结果链表
            curr.next=new ListNode(sum%10);
//          移动到下一个节点
            curr=curr.next;
            if (q!=null){
                q=q.next;
            }
            if (p!=null){
                p=p.next;
            }
        }
//        最后判断是否仍有进位,防止进位被遗漏
        if (carry>0){
            curr.next=new ListNode(carry);
        }
//        因为第一个节点是哑节点,
        return dummyHead.next;
    }

时间复杂度:O(max(m,n))

NO.204 计算质数 简单

QMwlp4.png

思路一:暴力法 双层for循环。1.第一层循环遍历逐个判断[2,n)。2.第二层循环判断参数是否为素数。:

public int countPrimes(int n){
    int count=0;
    for (int i=2;i<n;i++){
        if (isPrime(i))
            count++;
    }
    return count;
}

public boolean isPrime(int n){
    for(int i=2;i<n;i++){
        if (n%i==0)return false;
    }
    return true;
}

时间复杂度:O(n^2)

**可改进点:**例如,12=2*6、12=3*4、12=sqrt(12)*sqrt(12)、12=4*3、12=6*2,可以观察到后面就是前面两个数反过来,说明查找可以整除12的因子时只需要找到“一半”的位置即可,如果前“一半”没有可以整除的因子,那么后“一半”也没有,这个临界点“一半”就是sqrt(12)。所以上述isPrime()方法的循环条件可以写为“i*i<n”即可,该方法时间复杂度降到了O(sqrt(n))。

思路二:厄拉多塞筛法 不难想象,所有质数的倍数都不是质数。例如,2是质数,2的倍数4、6、8、10、12。。。都不是质数;3是质数,3的倍数6、9、12、15。。。都不是质数;可以看一下维基百科中一个厄拉多塞筛的gif图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AAS7gp6w-1575380399614)(https://s2.ax1x.com/2019/12/03/QMw11J.gif)]

这种方法大概就是“排除法”,每确定一个质数,就可以排除一批非质数,那么算法就可以这么写:

public int countPrimes(int n){
        boolean isPrimes[]=new boolean[n];
        Arrays.fill(isPrimes,true);
//		将所有质数的倍数设置为false
        for (int i=2;i<n;i++){
            for (int j=i;j<n;j+=i){
                isPrimes[j]=false;
            }
        }
        int count=0;
//      统计所有质数,即isPrimes[i]==true的为质数
        for (int i=2;i<n;i++){
            if (isPrimes[i])count++;
        }
        return count;
    }

上述算法还存在两处冗余:

  1. 在本题的暴利算法下说的:只需要判断到sqrt(n)即可。
  2. 例如,12不是质数,所以会被设置为false,但是12既是2的倍数,也是3的倍数,所以它被标记了两次。

解决上述两处冗余后的算法:

public int countPrimes(int n){
        boolean isPrimes[]=new boolean[n];
        Arrays.fill(isPrimes,true);
//        只需要判断小于sqrt(n)的数是否为质数即可,所以i*i<n
        for (int i=2;i*i<n;i++){
//            这样可以把质数i的整数倍都标记为false,但是仍然存在计算冗余。
//            比如n=25,i=4时算法会标记4×2=8,4×3=12等等数字,
//            但是这两个数字已经被i=2和i=3的2×4和3×4标记了。所以使用j=i*i减少此计算的冗余。
            for (int j=i*i;j<n;j+=i){
                isPrimes[j]=false;
            }
        }
        int count=0;
//      统计所有质数,即isPrimes[i]==true的为质数
//        这里要注意从2开始,因为0,1不是质数
        for (int i=2;i<n;i++){
            if (isPrimes[i])count++;
        }
        return count;
    }

厄尔拉塞筛法的时间复杂度:O(nloglogn)


学习算法,还是尽量去学习更优化的算法,只有这样才能从算法学习中得到更多锻炼。

发布了44 篇原创文章 · 获赞 22 · 访问量 1937

猜你喜欢

转载自blog.csdn.net/qq_42758551/article/details/103377268