小白刷题记录-大二下
文章目录
70爬楼梯(易)1
动态规划
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
看了一会,思考一个大方向。发现好像可以递归(这里想无论遇到什么题都应该思考一下大方向,或者说常见的算法分类),思考了一下递归出口,写了。超时,寄!图样图森破sometimesnaive。递归和dp好像存在某种联系,去查了一下,这个解释很好的解释了这道题。
class Solution {
public int climbStairs(int n) {
//貌似是if是不是判断耗了很多时间,这么多if太蠢了,还是dp好用。
if(n==2)
{
return 2;
}
if(n==1)
{
return 1;
}
return climbStairs(n-1)+climbStairs(n-2);
}
}
同样的思路,为什么递归就不会超时?可能是我判断耗了太多的内存了。之前确实没想到这一层。在题解中看到:DP其实就是空间换时间,所以这里空间复杂度会稍微高点!
我发现dp和循环似乎有某种联系,下面看到一题也是自下而上的dp循环
public int climbStairs(int n) {
int[] dp = new int[n + 1];
**这里还要注意出口的设置**
dp[0] = 1;
dp[1] = 1;
//自下而上,我记得之前学数据结构的时候好像没这印象 //啊,留个心眼再看吧
for(int i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
还有一种用「滚动数组思想」把空间复杂度优化成 O(1)O(1)。下面的代码中给出的就是这种实现。
public int climbStairs(int n) {
int p = 0, q = 0, r = 1;
for (int i = 1; i <= n; ++i) {
p = q;
q = r;
r = p + q;
}
return r;
}
206单链表的反转(易)2
链表
遍历原链表,用插入法来解决。关键点是要实现插入必须要有两个定位的指针,一个定位在原来的链表curr,另一个定位在反转的链表prev。特别要想清楚链表状态的变化。
细节:temp在外面定义,可以少消耗内存。
**没有head的链表**
class Solution {
public ListNode reverseList(ListNode head) {
ListNode cur = head;
ListNode pre = null;
ListNode temp = null;
while(cur!=null)
{
temp = cur.next;
cur.next = pre;
**与下面的区别,不需要插入,只需要移动指针
直接放在最前面,向前移动指针pre**
pre = cur;
**向后移动指针cur**
cur=temp;
}
return pre;
}
}
有head的链表
class Solution {
public ListNode reverseList(ListNode head) {
ListNode cur = head.next;
Listnode newhead = null;
ListNode temp = null;
while(cur!=null)
{
temp = cur.next;
cur.next = newhead.next;
**想清楚curr插在哪里?新链表标记节点的后面
所以curr.next=新链表标记节点.next,新链表标记节
点.next=curr,注意newhead是不变的**
newhead.next = cur;
cur = temp;
}
return newhead;
}
}
53最大子数组和(易)3
动态规划
给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
动态规划是自底向上的思路,动态规划的范围是大于递归的,常用自底向上的循环更新状态。
这题和买卖股票差不多,很容易想到维护一个包含最后一个元素的最大的数组和原来的最大值比较。但是包含最后一个元素的最大值怎么定义是关键:max(pre+i,i),这种可以试着想(带入):最开始的时候是怎么样的,怎么样可以维护。而且初始值的设置,需要特别关注题干,还有循环语句。
class Solution {
public int maxSubArray(int[] nums) {
int pre = 0;
int maxAns = nums[0];
for(int i:nums)
{
pre = Math.max(pre+i,i);
maxAns = Math.max(pre,maxAns);
}
return maxAns;
}
}
21合并两个有序链表(易)4
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode temp = null;
//首先做一个调整,保证最小的在list1
if (list1.val>list2.val)
{
temp = list1;
list1 = list2;
list2 = temp;
}
while(list1.next!=null && list2.next!=null)
{
//进入主循环中,保证lst2的第一个一定大于等于lst1的第一个
//如果list2符合插入条件,把list2插入list1
if(list2.val<=list1.next.val&&list2.val>=list1.val)
{
temp = list2.next;
//插入
list2.next = list1.next;
list1.next = list2;
list2 = temp;
//别忘了list1往前推进
list1 = list1.next;
}
//如果不符合插入条件,list2更大,lst1后移
else if(list2.val>list1.next.val)
{
list1 = list1.next;
}
}
if(list1.next == null)
{
list1.next = list2;
return list1;
}
//lst2为空了
else
{
return list1;
}
}
}
121买卖股票的最佳时机(易)5
动态规划
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
思路:长得像动态规划,由于之前的经验动态规划经常自底向上从0开始循环,然后寻找上一个状态和下一个状态之间的联系。假设知道上一个状态的最大值,那么下一个状态的最大值则是max(max,price[i]-min)。这类问题可以这么思考,假设知道前一个状态的答案是什么,要怎么得到当前的答案。(因为最小的时候答案是很明显的,所以只要掌握了这个规律就可以一步一步推到最终的答案)
class Solution {
public int maxProfit(int[] prices) {
// int max = prices[0];
int min = prices[0];
int max = 0;
for(int i:prices)
{
max = Math.max(i-min,max);
min = Math.min(min, i);
}
return max;
}
}
1两数之和&141环形链表(易)7
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
暴力枚举法两层循环略
hash表法:每遍历一个数,先判断结果-那个数在不在哈希表里,不在放到哈希表里,在就直接返回。哈希表的查找很快!
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> hashtable = new HashMap<Integer, Integer>();
for (int i = 0; i < nums.length; ++i) {
if (hashtable.containsKey(target - nums[i])) {
return new int[]{hashtable.get(target - nums[i]), i};
}
hashtable.put(nums[i], i);
}
return new int[0];
}
}
为什么上面用hashmap而下面用hashset呢?
因为下面的只要判断在不在,而上面的要找出具体的值。
hashset不是kv,hashmap是kv。
public class Solution {
public boolean hasCycle(ListNode head) {
Set<ListNode> seen = new HashSet<ListNode>();
while (head != null) {
if (!seen.add(head)) {
return true;
}
head = head.next;
}
return false;
}
}
剑指 Offer 61. 扑克牌中的顺子(易) 8
从若干副扑克牌中随机抽 5 张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王为 0 ,可以看成任意数字。A 不能视为 14。
这种条件判断的题之前没做过还挺恶心的,条件可以巧妙地转化为:没有重合,最大值-最小值<=5,因为王可以任意填充,所以满足这个条件就可以
class Solution {
public boolean isStraight(int[] nums) {
Set<Integer> repeat = new HashSet<>();
int max = 0, min = 14;
for(int num : nums) {
if(num == 0) continue; // 跳过大小王
max = Math.max(max, num); // 最大牌
min = Math.min(min, num); // 最小牌
if(repeat.contains(num)) return false; // 若有重复,提前返回 false
repeat.add(num); // 添加此牌至 Set
}
return max - min < 5; // 最大牌 - 最小牌 < 5 则可构成顺子
}
}
283. 移动零(易)9
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
双指针法,右指针指向第一次出现0后面第一个不为0的元素
左指针指向第一个0
难点在条件判断,哪些条件是必要的,哪些条件是多余的,很容易想多!初始值的设定也很重要,一般会想到设置数组的第一个元素,但是设置0就可以规避很多问题!
看似简单其实要注意两点细节:条件判断和初始值的设置!这两点往往是相辅相成的,好的初始值设置可以让边界条件或者特殊条件 的判断更加简单。
class Solution {
public void moveZeroes(int[] nums) {
int n = nums.length, left = 0, right = 0;
while (right < n) {
if (nums[right] != 0) {
swap(nums, left, right);
left++;
}
right++;
}
}
public void swap(int[] nums, int left, int right) {
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
}
}
20. 有效的括号(易) 10
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
思路:之前就有一个要用栈的印象,不过实操起来还是有点麻烦,踩了坑,对于判断条件还是应该额外谨慎。这里包括返回true的条件,应该时stack为空而不是循环跑完。以及先给出右括号的条件应该被排除。
class Solution {
public boolean isValid(String s) {
Stack<Character> stack = new Stack<Character>();
char []str = s.toCharArray();
for(int i=0;i<str.length;i++)
{
if(str[i]=='('|str[i]=='['|str[i]=='{')
{
stack.push(str[i]);
}
**不一定是要左边先入,也可以右边先入直接就寄了,那 右边先入进不去栈所以为空。
如果push之后为空就说明挂了
没注意栈为空:**}(
if(stack.isEmpty())
{
return false;
}
if(str[i]==')')
{
char temp = stack.pop();
if (temp == '(')
{
continue;
}
else
{
return false;
}
}
else if(str[i]==']')
{
char temp = stack.pop();
if (temp == '[')
{continue;
}
else
{
return false;
}
}
else if(str[i]=='}')
{
char temp = stack.pop();
if (temp == '{')
{
continue;
}
else
{
return false;
}
}
}
//不能直接returntrue,走完循环不代表没问题,这个时候如果栈里还有东西也是错的。
return stack.isEmpty();
}
}