剑指offer-leetcode刷题总结(一)19.9.8-9.21

目录

剑指offer普通题: 

2019/9/8:  二维数组查找

2019/9/8:  替换空格

2019/9/9:  smallRotateNum

2019/9/9:  斐波那契系列

2019/9/9:  twoStack

2019/9/11:  二进制中1的个数

剑指offer链表题:

2019/9/20:  从尾到头打印链表

2019/9/20:  链表反转

2019/9/20:  合并两个排序的链表

Leetcode 普通题

2019/9/14:  搜索旋转排序数组I

2019/9/15:  搜索旋转排序数组II

2019/9/16:  两数之和

2019/9/19:  移动零

Leetcode链表题

2019/9/19:  两数相加

近期刷题总结



剑指offer普通题: 

2019/9/8:  二维数组查找

题目描述

        在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

题解:首先是两种思路

① 假设数组是M*N的,每一行用二分搜索,这样最坏的时间复杂度就是M*Log2N,但这种方法只用到了一个条件:从左到右有序。但并不是题目希望我们的方法

② 二分思想,从左下角开始遍历,或者从右上角开始遍历,这样最坏的时间复杂度就是O(n),下面的代码按照左下开始:(注意:提交时一直提示段错误,思考了很久才发现,要先判断数组是否存在

class Solution {
public:
    bool Find(int target, vector > array) {

        int row = array.size();
        int column = array[0].size();
        int key_row = row-1, key_column = 0;
        //判断是否存在!!!
        if(row ==0 || column == 0)
            return 0;
        if(target < array[0][0] || target > array[row-1][column-1])
            return 0;
        //应用本题所给信息,从左下边开始找,为的是利用一个二分的思想
        while(key_column < column && key_row >= 0){
            if(target == array[key_row][key_column])
                return 1;
            else if(target > array[key_row][key_column])
                key_column++;
            else if(target < array[key_row][key_column])
                key_row--;
        }
        return 0;
    }
};

 

2019/9/8:  替换空格

题目描述

        请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy

题解

replace是java中String的方法,而不是StringBuilder的

注意返回值类型和参数类型不一致

public class Solution {
    public String replaceSpace(StringBuffer str) {
        String str1 = str.toString();
        str1 = str1.replace(" ","%20");//replace是String的方法
        return str1;
    }
}

 

2019/9/9:  smallRotateNum

题目描述    

        把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0

题解    (具体见博文点击:旋转数组的最小数字)

import java.util.ArrayList;
public class Solution {
    public int minNumberInRotateArray(int [] array) {
        if(array.length == 0)
            return 0;

        //第一种,最简单的方式:
        int min = 2147483647;
        for(int i=0;i
288 ms 28564K
//第二种:熟悉一下堆结构,稍复杂
//前几天学了堆,首先想到把数组想象成堆结构,小的数向上调整
//从第一个数据开始;
import java.util.ArrayList;
public class Solution {
    public int minNumberInRotateArray(int [] array) {
        if(array.length == 0)
            return 0;
        int i,t;
        for(i=0; i
494 ms 28388K

 

2019/9/9:  斐波那契系列

        遇到类似问题,先找规律,得到通式,然后寻找递归出口。接下来可以考虑具体实现方式:可以从时间复杂度方面去改进。因为题目有所限制,所以用数组记录的方式比较难以实现,可以看我斐波那契那篇文章,里面有一些改进方式

题目描述 I 

       大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0)。

n<=39

题解  (具体见博文点击:深入斐波那契数列)

public int Fibonacci(int n) {
        //先注意返回值类型int,参数类型int
        //题目要求是求项数值,不是求和,而且又是int,所以用不到BigInteger
        //第0项为0,那么0,1,1,2,3,5,8……n<=39
        //数据量很大,而且时间有限制,所以考虑用数组存下每个值
        int arr[40];
        if(n==0)
            return 0;
        if(n==1 || n==2)
            return 1;
        arr[n]=Fibonacci(n-1)+Fibonacci(n-2);
    }

题目描述 II

        一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。

题解

int jumpFloor(int target) {
int t1=1,t2=2,total=0;
if (target==1||target==2) return target;
for(int i=3;i<=target;i++) {
    total=t1+t2;
    t1=t2;
    t2=total;
    }
   return total;
}
//递推法:时间复杂度为O(n),空间上减少了开销

题目描述 III

        一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法

题解

public class Solution {
    public int JumpFloorII(int target) {
        int tot=0;
        if(target==1 || target==1){
            return target;
        }
        else for(int i=1;i<=target-1;i++) {
            tot += JumpFloorII(i);
        }
        return tot+1;
    }
}

 

2019/9/9:  twoStack

题目描述

用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型

题解:C++和java版本略有不同  (具体见博文点击:TwoStack)

//code-C++版:

class Solution
{
public:
    void push(int node) {
        //top() 返回值,不删除
        //注意:值不只一个,数值全部入栈后,开始出栈
        stack1.push(node);
    }

    int pop() {
        int result;
        if(stack1.empty() && stack2.empty()){
            cout<<"empty!"< stack1;
    stack stack2;
};
//code-java版本
import java.util.Stack;

public class Solution {
    Stack stack1 = new Stack();
    Stack stack2 = new Stack();
     public void push(int node) {
        stack1.push(node);
    }
     
    public int pop() {
        if(stack1.empty()&&stack2.empty()){
            throw new RuntimeException("Queue is empty!");
        }
        if(stack2.empty()){
            while(!stack1.empty()){
                stack2.push(stack1.pop());
            }
        }
        return stack2.pop();
    }
}

C++版本和java版本有差异:

C++:pop() 弹出数据,无返回;一般用top()来做返回

java:pop() 弹出数据,有返回;没有top()函数

 

2019/9/11:  二进制中1的个数

题目描述

输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示

题解

public class Solution {
    public int NumberOf1(int n) {
        //负数补码?= 反码+1,java的toBinaryString能直接转换
         int count=0;
         String str = Integer.toBinaryString(n);//负数直接得到补码,正数是其本身
         char[] b_num = str.toCharArray();        //转为字符串
         for(int i=0;i

 

剑指offer链表题:

2019/9/20:  从尾到头打印链表

题目描述

输入一个链表,按链表从尾到头的顺序返回一个ArrayList

题解   (具体见博文点击:链表反转)

这种是借助一个栈来做中间存储的方法,最后逆序弹出

/**
*    public class ListNode {
*        int val;
*        ListNode next = null;
*
*        ListNode(int val) {
*            this.val = val;
*        }
*    }
*/
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;
public class Solution {
    public ArrayList printListFromTailToHead(ListNode listNode) {
        ArrayList ArrayList1 = new ArrayList();
        ListNode list;
        
        list = listNode;
        Stack sta = new Stack();
        while(listNode!=null){
            sta.add(listNode.val);
            listNode = listNode.next;
        } 
        while(!sta.isEmpty()){
            ArrayList1.add(sta.pop());
        }
        return ArrayList1;
    }
}

2019/9/20:  链表反转

/*
public class ListNode {
    int val;
    ListNode next = null;
    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode ReverseList(ListNode head) {
        ListNode pre = null;
        ListNode cur = head;
        ListNode tmp = null;
        while(cur!=null){
            tmp = cur.next;//先更新后指针
            cur.next = pre;//反向
            pre = cur;//更新前指针
            cur = tmp;//更新当前指针
        }
        return pre;
    }
}

 

2019/9/20:  合并两个排序的链表

题目描述

输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

题解   (具体见博文点击:合并两个排序的数组)

我的解法是分为三步

第一步分离出特殊情况,如开始就出现空的情况

第二步:正常情况分离出来处理

第三步:遇到最后边界部分处理

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

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode Merge(ListNode list1,ListNode list2) {
        if(list1 == null && list2 == null)
            return null;
        else if(list1 == null)
            return list2;
        else if(list2==null)
            return list1;
        
        ListNode cur = new ListNode(0);
        ListNode list = cur;
        //只要出现一个是null就退出,临界情况交给后面处理
        while(list1!=null && list2!=null){
            int x = list1.val;
            int y = list2.val;
            if(x>y){
                cur.next = new ListNode(y);
                list2 = list2.next; //list2继续往后走,list1不动
            }
            else {
                cur.next = new ListNode(x);
                list1 = list1.next;
            }
            cur = cur.next;
           }
           //到临界点,有一个是null或者两个都是null
            if(list1!=null && list2==null)
                cur.next = list1;
        	else if(list1==null && list2!=null)
        		cur.next = list2;
            else if(list1==null && list2==null)
                cur.next = null;
            return list.next;
       }
}

运行时间:28ms

占用内存:9668k

 

大佬的做法:递归方式

/*
public class ListNode {
    int val;
    ListNode next = null;
    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode Merge(ListNode list1,ListNode list2) {
        //递归版本
        if(list1 == null)
            return list2;
        if(list2 == null)
            return list1;
        if(list1.val <= list2.val){
            list1.next = Merge(list1.next,list2);
            return list1;
        }
        else {
            list2.next = Merge(list1,list2.next);
            return list2;
         }
       }
}

运行时间:31ms

占用内存:9316k

Leetcode 普通题

2019/9/14:  搜索旋转排序数组I

题目描述:(难度中等) leetcode33

假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。

你可以假设数组中不存在重复的元素。

你的算法时间复杂度必须是 O(log n) 级别。

示例 1:

输入: nums = [4,5,6,7,0,1,2], target = 0

输出: 4

示例 2:

输入: nums = [4,5,6,7,0,1,2], target = 3

输出: -1


题解  (具体过程见博文点击:搜索旋转排序数组)

见到 log(N)的第一想法是二分和堆排的插入操作,下面这种就是借用了堆结构的建立过程的思想

class Solution {
    public int search(int[] nums, int target) {
        
        int i;
        
        if(nums.length == 0)
            return -1;

        for(i=0; i<=nums.length/2; i++){ //对于i的右边出错过,没有划定好
        //原本我写的是当数据是i<=nums.length/2-1,但是用例196个错了一个在【1】1的时候。
            if(nums[i] == target)
                return i;
        //原本在这里没有考虑2*i+1和2*i+2有没有越界的问题,所以应该在做提前把所有的要求先写下来,特别是第一时间想到的边界问题
            if(2*i+1<=nums.length-1 && nums[2*i+1] == target)
                return 2*i+1;
            if(2*i+2<=nums.length-1 && nums[2*i+2] == target)
                return 2*i+2;
        }
        return -1;
    }
}

执行用时 :2 ms, 在所有 Java 提交中击败了51.73%的用户

内存消耗 :35.7 MB, 在所有Java提交中击败了87.31%的用户

大佬的做法:充分利用了二分的思想

题目要求O(logN)的时间复杂度,基本可以断定本题是需要使用二分查找,怎么分是关键
由于题目说数字了无重复,举个例子
1 2 3 4 5 6 7 可以大致分为两类,
第一类 2 3 4 5 6 7 1这种,也就是nums[start] <= nums[mid]。此例子中就是2 <= 5
这种情况下,前半部分有序。因此如果 nums[start] <=target nums[mid]。此例子中就是6 > 2
这种情况下,后半部分有序。因此如果 nums[mid] = nums[start] && target < nums[mid]) {
                    end = mid - 1;
                } else {
                    start = mid + 1;
                }
            } else {
                if (target <= nums[end] && target > nums[mid]) {
                    start = mid + 1;
                } else {
                    end = mid - 1;
                }
            }
        }
        return -1;
    }

执行结果:通过  显示详情

执行用时 :1 ms, 在所有 Java 提交中击败了99.84%的用户

内存消耗 :36.3 MB, 在所有 Java 提交中击败了85.46%的用户

 

2019/9/15:  搜索旋转排序数组II

题目描述:(难度中等)leetcode 81

假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,0,1,2,2,5,6] 可能变为 [2,5,6,0,0,1,2] )。

编写一个函数来判断给定的目标值是否存在于数组中。若存在返回 true,否则返回 false。

示例 1:

输入: nums = [2,5,6,0,0,1,2], target = 0

输出: true

示例 2:

输入: nums = [2,5,6,0,0,1,2], target = 3

输出: false

题解

        这题和上面的是有点区别的,因为涉及到重复的元素,用第二种做法就会有点问题。所以我先试了第一种堆的做法,结果惊人,执行效率很高,但内存消耗很大。但我不太懂,我用的都是题目给的内存数组,为啥内存消耗这么大?
第一种:我想到的堆方法,其次后来学到链表,通过双指针也可以很快找到中点和末尾,减少查询的时间复杂度

class Solution {
    public boolean search(int[] nums, int target) {
        int i;
        
        if(nums.length == 0)
            return false;

        for(i=0; i<=nums.length/2; i++){ 
            if(nums[i] == target)
                return true;
            if(2*i+1<=nums.length-1 && nums[2*i+1] == target)
                return true;
            if(2*i+2<=nums.length-1 && nums[2*i+2] == target)
                return true;
        }
        return false;
    }
}

执行结果:通过   显示详情

执行用时 :1 ms, 在所有 Java 提交中击败了100.00%的用户

内存消耗 :39.7 MB, 在所有 Java 提交中击败了10.72%的用户

 

第二种还是上面那位大佬的二分做法,确实精辟,充分利用条件

public boolean search(int[] nums, int target) {
        if (nums == null || nums.length == 0) {
            return false;
        }
        int start = 0;
        int end = nums.length - 1;
        int mid;
        while (start <= end) {
            mid = start + (end - start) / 2;
            if (nums[mid] == target) {
                return true;
            }
            if (nums[start] == nums[mid]) {//很妙的一步
                start++;
                continue;
            }
            //前半部分有序
            if (nums[start] < nums[mid]) {
                //target在前半部分
                if (nums[mid] > target && nums[start] <= target) {
                    end = mid - 1;
                } else {  //否则,去后半部分找
                    start = mid + 1;
                }
            } else {
                //后半部分有序
                //target在后半部分
                if (nums[mid] < target && nums[end] >= target) {
                    start = mid + 1;
                } else {  //否则,去后半部分找
                    end = mid - 1;
                }
            }
        }
        //一直没找到,返回false
        return false;
    }

执行结果:通过  显示详情

执行用时 :2 ms, 在所有 Java 提交中击败了66.63%的用户

内存消耗 :38.9 MB, 在所有 Java 提交中击败了24.71%的用户

 

2019/9/16:  两数之和

题目描述  leetcode 1   

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。

示例:

给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/two-sum

题解

class Solution {
   public static int[] twoSum(int[] nums, int target) {
        Map  map = new HashMap<>();
        for(int i=0;i

这题我学到了hashmap相关的一点用法,且有了一个初步了解

 

2019/9/19:  移动零

题目描述:leetcode 283

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

示例:

输入: [0,1,0,3,12]

输出: [1,3,12,0,0]

说明:

必须在原数组上操作,不能拷贝额外的数组。

尽量减少操作次数。

题解 

有点像荷兰国旗问题,有一个小于区域的指针,只要后面遍历的满足条件,就交换位置,然后往后小于区域扩一个位置

class Solution {
    public void moveZeroes(int[] nums) {
        int i,left=0,t;
        for(i=0;i

执行结果:通过  显示详情

执行用时 :1 ms, 在所有 Java 提交中击败了97.12%的用户

内存消耗 :40.8 MB, 在所有 Java 提交中击败了50.25%的用户

 

Leetcode链表题

2019/9/19:  两数相加

题目描述:leetcode 2

给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。

如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。

您可以假设除了数字 0 之外,这两个数都不会以 0 开头。

示例:

输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)

输出:7 -> 0 -> 8

原因:342 + 465 = 807

题解

分析:

        //第一种:用队列存储,每次下一位相加,从尾部队列插入,最后头部弹出,这样加大了空间复杂度。

        //第二种:直接相加,覆盖l1,往右做加法。

        //首先显式条件:非空,非负,整数,逆序

        //隐式条件:可能是两个长度不一样的,意味着可能null有先后(是新开辟一个链表存储结果?还是继续选择其中一个?)

        //这种抠边界的问题短时间比较难做出来,需要仔细,第一步得找共性,都要判断是否超10,那就设置一个标志位,每次进入先做处理

如果有兴趣想看我的思路代码,虽然很差,但是是自己一步一个坑踩过来的,也做了一些总结,可以到单独的篇章去看。(见博文点击:两数相加)下面贴出官方的漂亮代码解法:

public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
    ListNode dummyHead = new ListNode(0);
    ListNode p = l1, q = l2, curr = dummyHead;
    int carry = 0;
    while (p != null || q != null) {
        int x = (p != null) ? p.val : 0;
        int y = (q != null) ? q.val : 0;
        int sum = carry + x + y;
        carry = sum / 10;
        curr.next = new ListNode(sum % 10);
        curr = curr.next;
        if (p != null) p = p.next;
        if (q != null) q = q.next;
    }
    if (carry > 0) {
        curr.next = new ListNode(carry);
    }
    return dummyHead.next;
}

近期刷题总结

最后给自己鼓励一下,这段时间我感觉自己很努力,有以下几个方面:

      1. 遇到隐约有想法的,不管自己写的有多差,先去实现。

      2. 遇到题目中的bug,能静下心来慢慢查找,并且自己写完题解后,认真地去看其他朋友优秀的代码,分析自己的不足;

      3. 对解出来的题目,做了总结,在后期的题解中也有所体现;

      4. 遇到不会的,如链表这块内容,我先对链表的知识做了分解,划分成具体的小任务。通java一步一步靠自己先前或多或少的理解写出代码,分析自己是卡在哪里,最后对它有了一定深入的理解。(可以查看我的理解过程代码,见博文点击 链表的学习过程

      5. 做题养成一个好习惯,打开画图软件(帮助自己分析),写好注释(题目的显性和隐性要求)

除了进步也有缺陷:

      6. 现在的缺陷是刷题时间太长,题数和吸收量不高,所以需要提高效率,对时间要有一个划定。

现在我把这一周左右的题目做一个归总,供日后回顾复习。但这里不是最详细的,只是大概的题解,如果想看详细的,可以关注我的博客其它文章,里面有我对每个题的题解和思路分析

坚持住,继续加油!

 

 

 

 

 

你可能感兴趣的:(算法训练,Leetcode,剑指offer)