目录
剑指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: 两数相加
近期刷题总结
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
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
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%的用户
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. 现在的缺陷是刷题时间太长,题数和吸收量不高,所以需要提高效率,对时间要有一个划定。
现在我把这一周左右的题目做一个归总,供日后回顾复习。但这里不是最详细的,只是大概的题解,如果想看详细的,可以关注我的博客其它文章,里面有我对每个题的题解和思路分析
坚持住,继续加油!