关于JAVA的学习出了看视频以外,那就是刷题了,朋友们,你们有没有过这样的感觉,在网上看了视频过后感觉自己什么都听懂了,但就是写题和做项目时无从下手,或者就是因为某个细节一直错一直改,那背后的原因是什么呢?四个字——题刷少了,这里新一建议去
Leetcode
看看,那里的题库资源很丰富,并且在全球都有广泛涉猎。不仅如此,这里还有 课程 + 刷题 + 面经 + 求职 + 讨论区分享解题思路,用过的人都说好
除此之外,我的建议是初学者从简单题
开始练习,因为简单题是一切题的基础,一切的困难题都是从简单题衍生而来的,每天刷那么2~3题,后期再慢慢刷中等题,困难题,经过一段时间后会有很不错的提升
此外,在我们有一定的提升之后,我们便可以去刷剑指offer
了,在这里预祝各位大学生以及初学者都拿到自己满意的offer!
做题链接戳这里:234.回文链表
给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。
输入:head = [1,2,2,1]
输出:true
输入:head = [1,2]
输出:false
● 链表中节点数目在范围[1, 105] 内
● 0 <= Node.val <= 9
进阶:你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?
常规思路:
要判断回文链表,我们首先想到的是倒排,然后同时遍历,如果最后都能一直匹配上那么它就是回文链表,那么关键是我们怎么把链表倒过来呢?在定义一个链表尾插?不不不,既然这是单链表我们用栈是不是会更好呢,遍历链表的同时入栈即可,后续直接在遍历一遍链表,看出栈元素是否匹配即可
class Solution {
public boolean isPalindrome(ListNode head) {
Stack<Integer> stack = new Stack<>();
ListNode node = head;
while (node != null){
stack.push(node.val);
node = node.next;
}
node = head;
while (node != null){
if (!stack.empty() && node.val != stack.pop()){
return false;
}
node = node.next;
}
return stack.empty();
}
}
快慢指针压半栈
上述方法确实可以,也挺简单,但是这时间复杂度实在有点不够看,那么我们有更好的方法吗?当然有,既然它满足回文链表,那么我们就找一个中间节点,把这个节点之前的数据压栈,然后退出循环,然后拿栈顶元素跟之后的元素比较即可,入过不相等退出循环,返回栈空即可。
class Solution {
public boolean isPalindrome(ListNode head) {
Stack<Integer> stack = new Stack<>();
ListNode fast = head;
ListNode slow = head;
while (fast != null && fast.next != null){
stack.push(slow.val);
fast = fast.next.next;
slow = slow.next;
}
if (fast != null && fast.next == null){
slow = slow.next;
}
while (slow != null){
if (!stack.empty() && slow.val == stack.peek()){
stack.pop();
}else{
break;
}
slow = slow.next;
}
return stack.empty();
}
}
加油!我们的时间已经减少了接近一半了,我们想想压栈弹出肯定是需要时间的,我们能否模拟栈呢,将其压栈弹出的部分再优化掉,当然可以,只需生成一个傀儡节点即可,然后利用快慢指针将其头插,最后遍历新生成的的两个链表即可(实际上是将该链表分割成两部分,时间复杂度O(n),空间复杂度O(1)),缺点就是代码量较大。
class Solution {
public boolean isPalindrome(ListNode head) {
if (head == null){
return false;
}
ListNode fast = head;
ListNode slow = head;
ListNode newHead = new ListNode(-1);
ListNode tmp = newHead;
ListNode cur = head;
while (fast != null && fast.next != null){
fast = fast.next.next;
cur = cur.next;
slow.next = tmp;
tmp = slow;
slow = cur;
}
if (fast != null && fast.next == null){
slow = slow.next;
}
while (slow != null){
if (slow.val != tmp.val){
return false;
}
slow = slow.next;
tmp = tmp.next;
}
return true;
}
}
最后我们来说说这个方法:数学文化,博大精深,八行代码,完全爆破
class Solution {
public boolean isPalindrome(ListNode head) {
int s1 = 0,s2 = 0,t = 1;
while(head != null) {
s1 = s1*10 + head.val;
s2 = s2 + t*head.val;
t = t*10;
head = head.next;
}
return s1 == s2;
}
}
做题链接戳这里,682.棒球比赛
你现在是一场采用特殊赛制棒球比赛的记录员。这场比赛由若干回合组成,过去几回合的得分可能会影响以后几回合的得分。
比赛开始时,记录是空白的。你会得到一个记录操作的字符串列表 ops,其中 ops[i] 是你需要记录的第 i 项操作,ops 遵循下述规则:
整数 x - 表示本回合新获得分数 x
“+” - 表示本回合新获得的得分是前两次得分的总和。题目数据保证记录此操作时前面总是存在两个有效的分数。
“D” - 表示本回合新获得的得分是前一次得分的两倍。题目数据保证记录此操作时前面总是存在一个有效的分数。
“C” - 表示前一次得分无效,将其从记录中移除。题目数据保证记录此操作时前面总是存在一个有效的分数。
请你返回记录中所有得分的总和。
输入:ops = [“5”,“2”,“C”,“D”,“+”]
输出:30
解释:
“5” - 记录加 5 ,记录现在是 [5]
“2” - 记录加 2 ,记录现在是 [5, 2]
“C” - 使前一次得分的记录无效并将其移除,记录现在是 [5].
“D” - 记录加 2 * 5 = 10 ,记录现在是 [5, 10].
“+” - 记录加 5 + 10 = 15 ,记录现在是 [5, 10, 15].
所有得分的总和 5 + 10 + 15 = 30
示例2
输入:ops = [“5”,“-2”,“4”,“C”,“D”,“9”,“+”,“+”]
输出:27
解释:
“5” - 记录加 5 ,记录现在是 [5]
“-2” - 记录加 -2 ,记录现在是 [5, -2]
“4” - 记录加 4 ,记录现在是 [5, -2, 4]
“C” - 使前一次得分的记录无效并将其移除,记录现在是 [5, -2]
“D” - 记录加 2 * -2 = -4 ,记录现在是 [5, -2, -4]
“9” - 记录加 9 ,记录现在是 [5, -2, -4, 9]
“+” - 记录加 -4 + 9 = 5 ,记录现在是 [5, -2, -4, 9, 5]
“+” - 记录加 9 + 5 = 14 ,记录现在是 [5, -2, -4, 9, 5, 14]
所有得分的总和 5 + -2 + -4 + 9 + 5 + 14 = 27
● 1 <= ops.length <= 1000
● ops[i] 为 “C”、“D”、“+”,或者一个表示整数的字符串。整数范围是 [-3 * 104, 3 * 104]
● 对于 “+” 操作,题目数据保证记录此操作时前面总是存在两个有效的分数
● 对于 “C” 和 “D” 操作,题目数据保证记录此操作时前面总是存在一个有效的分数
我们读了一遍题过后,发现共有四种操作,每种操作会对列表内元素做修改,那我们何不如用栈来模拟这个列表,利用栈的压栈和弹出来分别实现这四种操作,注意最后要检查栈是否为空,不为空结果需加上栈内剩余元素,还要注意输入的是字符串数组,我们需要压栈是需要强转为int型
class Solution {
public int calPoints(String[] operations) {
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < operations.length; i++) {
String str = operations[i];
switch (str){
case "+":
int num1 = stack.pop();
int num2 = stack.peek();
stack.push(num1);
stack.push(num1 + num2);
break;
case "D":
int num = stack.peek() * 2;
stack.push(num);
break;
case "C":
stack.pop();
break;
default:
stack.push(Integer.parseInt(str));
break;
}
}
int ret = 0;
while (!stack.empty()){
ret += stack.pop();
}
return ret;
}
}
数组模拟栈
还是跟上一道题一个道理,压栈弹出是需要时间的,我们用数组模拟栈即可实现访问元素的复杂度为线性
class Solution {
public int calPoints(String[] ops) {
int[] arr = new int[ops.length];
int i=0;
for(String s:ops){
switch (s){
case "+":arr[i]=arr[i-1]+arr[i-2];i++;break;
case "D":arr[i]=2*arr[i-1];i++;break;
case "C":arr[i-1]=0;i--;break;
default:
arr[i]=Integer.valueOf(s);
i++;
}
}
int sum=0;
for (int j = 0; j <arr.length ; j++) {
sum+=arr[j];
}
return sum;
}
}
做题链接戳这里:844.比较含退格的字符串
给定 s 和 t 两个字符串,当它们分别被输入到空白的文本编辑器后,如果两者相等,返回 true 。# 代表退格字符。
注意:如果对空文本输入退格字符,文本继续为空。
输入:s = “ab#c”, t = “ad#c”
输出:true
解释:s 和 t 都会变成 “ac”
示例2
输入:s = “ab##”, t = “c#d#”
输出:true
解释:s 和 t 都会变成 “”。
示例3
输入:s = “a#c”, t = “b”
输出:false
解释:s 会变成 “c”,但 t 仍然是 “b”。
● 1 <= s.length, t.length <= 200
● s 和 t 只含有小写字母以及字符 ‘#’
进阶:你可以用 O(n) 的时间复杂度和 O(1) 的空间复杂度解决该问题吗?
常规思路
怎么办?这是比较两个字符串啊,而且还包含退格符,我们仔细想想,我们用栈试试,遇到普通元素压栈,遇到退格符弹出,一个栈不够,那就两个!最后直接两个栈依次出栈比较即可
class Solution {
public boolean backspaceCompare(String s, String t) {
Stack<Character> stack1 = new Stack<>();
Stack<Character> stack2 = new Stack<>();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == '#'){
if (!stack1.empty()) {
stack1.pop();
}
}else{
stack1.push(c);
}
}
for (int i = 0; i < t.length(); i++) {
char c = t.charAt(i);
if (c == '#'){
if (!stack2.empty()) {
stack2.pop();
}
}else{
stack2.push(c);
}
}
while (!stack1.empty() && !stack2.empty()){
if (stack1.peek() == stack2.peek()){
stack1.pop();
stack2.pop();
}else{
break;
}
}
return stack1.empty() && stack2.empty();
}
}
优化思想
首先,我们两个字符串执行了同样的代码,那么干嘛不将它封装成一个方法,然后两个字符串分别调用即可,此外,还是我们的统一思想:压栈弹出浪费时间,数组模拟节省时间
class Solution {
public boolean backspaceCompare(String s, String t) {
return getString(s).equals(getString(t));
}
public String getString(String s){
char[] ret = s.toCharArray();
int n = ret.length;
int slow = 0;
for (int fast = 0; fast < n; fast++) {
if (ret[fast] == '#'){
if (slow > 0){
slow--;
}
}else{
ret[slow++] = ret[fast];
}
}
return new String(ret,0,slow);
}
}