上一篇 : LeetCode初级算法训练-字符串
下一篇 : LeetCode初级算法训练-树
本来想重初中级和企业面试算法开始的,但是最后还是选择从基础的开始,因为我们并不是为了刷题而刷题,而是在刷题过程中锻炼一种算法思维,在大量的训练之后形成一种对算法的独特见解,培养那种对算法的的敏感度,看到题目,大脑中可以浮现一个解题蓝图,而且从初级开始慢慢建立信心,而且这也是在为后边复杂算法的解题思路打基础。
LeetCode初级算法简介
如果你也想训练自己的算法思维,也可以加入我,从初级算法开始,开启你的算法之旅:初级算法。
自己的一些思考:不要在看完题目后直接就看答案,然后去背题,这样行成的算法记忆是不牢固的,一定要有自己的思考;而且不要一开始就在IDEA上边去写,一定试着自己在leetCode提供的白板上边写一遍最后在放到IDEA上边去执行看有什么问题,以便巩固你的基础API的使用和熟练度;还有一点就是大胆些,不是面试我们试错成本低,尽可能把我们的想法融入到代码中
因篇幅问题,博客中只列出示例和自己的解题答案,详细可以直接点击题目查看。
请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点,你将只被给定要求被删除的节点。
现有一个链表 – head = [4,5,1,9],它可以表示为:
示例 1:
输入: head = [4,5,1,9], node = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
示例 2:
输入: head = [4,5,1,9], node = 1
输出: [4,5,9]
解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.
说明:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public void deleteNode(ListNode node) {
node.val = node.next.val;
node.next = node.next.next;
}
}
执行用时:0 ms
内存消耗:39.5 MB
单链表从当前节点是找不到前边的节点的,所以这个时候我们考虑把当前的节点值换位下个节点的值,他的下个节点切换为下个节点的节点。(禁止套娃)。
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
示例:
给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.
说明:
给定的 n 保证是有效的。
进阶:
你能尝试使用一趟扫描实现吗?
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode fakeNode = new ListNode(0);
fakeNode.next = head;
ListNode target = head;
int count = 1;
while(target.next != null) {
target = target.next;
count ++;
}
int k = 0;
target = fakeNode;
while(k != count - n) {
target = target.next;
k++;
}
target.next = target.next.next;
return fakeNode.next;
}
}
执行用时:1 ms
内存消耗:37.8 MB
增加一个假的第一个节点是为了防止极端的一个节点或者最后一个节点和第一个节点时的情况,而且最后我们要返回头结点。两次循环可以搞定。
但是还有一种方式是双指针的方式。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode fakeNode = new ListNode(0);
fakeNode.next = head;
ListNode target = fakeNode;
for(int i = 0; i < n; i++) {
target = target.next;
}
ListNode result = fakeNode;
while(target.next != null) {
result = result.next;
target = target.next;
}
result.next = result.next.next;
return fakeNode.next;
}
}
208 / 208 个通过测试用例
状态:通过
执行用时:1 ms
内存消耗:38.3 MB
要注意就是第一个辅助点的添加很重要。
反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
进阶:
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
if(head == null) return null;
ListNode nextv = head.next;
ListNode curv = head;
ListNode prev = null;
while(curv != null) {
curv.next = prev;
prev = curv;
curv = nextv;
if (nextv != null) {
nextv = nextv.next;
}
}
return prev;
}
}
27 / 27 个通过测试用例
状态:通过
执行用时:0 ms
内存消耗:39.7 MB
三个参数进行交换,主要是判断啥时候停止。我们应该判断的是curv因为他每次其实是赋值为nextv,当他为null的时候说明在上一次的轮询中,nextv.next已经为null了,而且nextv要判空。
双指针方法:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
if(head == null || head.next == null) return head;
ListNode temp = null;
ListNode cur = head;
ListNode pre = null;
while(cur != null){
temp = cur.next;
cur.next = pre;
pre = cur;
cur = temp;
}
return pre;
}
}
递归:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
if(head == null || head.next == null) return head;
ListNode cur = reverseList(head.next);
head.next.next = head;
head.next = null;
return cur;
}
}
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode listNode = new ListNode(-1);
ListNode preNode = listNode;
while (l1 != null && l2 != null) {
if (l1.val >= l2.val) {
preNode.next = l2;
l2 = l2.next;
} else {
preNode.next = l1;
l1 = l1.next;
}
preNode = preNode.next;
}
preNode.next = (l1 == null ? l2 : l1);
return listNode.next;
}
}
208 / 208 个通过测试用例
状态:通过
执行用时:1 ms
内存消耗:39.5 MB
这个题跟合并两个排序树组一致,相当于已经给我们提供了两个指针,我们只需要对比大小把节点放到新的一个链表中,并返回头结点的next。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public boolean isPalindrome(ListNode head) {
if (head == null || head.next == null) return true;
ListNode target1 = head;
ListNode target2 = head;
//计算数目
int count = 0;
while (target1 != null) {
target1 = target1.next;
count++;
}
//找出前半部分最后一个
int k = 1;
target1 = head;
while (k < count / 2) {
target1 = target1.next;
k++;
}
//找到后半部分第一个
target2 = (count % 2 == 0 ? target1.next : target1.next.next);
//断开链表
target1.next = null;
//反转前半部分链表
ListNode listNode = reverseList(head);
//逐个判断是否相等
while (listNode != null) {
if (target2.val != listNode.val) {
return false;
}
listNode = listNode.next;
target2 = target2.next;
}
return true;
}
public ListNode reverseList(ListNode head) {
if(head == null || head.next == null) return head;
ListNode cur = reverseList(head.next);
head.next.next = head;
head.next = null;
return cur;
}
}
26 / 26 个通过测试用例
状态:通过
执行用时:2 ms
内存消耗:43.8 MB
//TODO 待补充一个快慢指针
给定一个链表,判断链表中是否有环。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
进阶:
你能用 O(1)(即,常量)内存解决此问题吗?
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public boolean hasCycle(ListNode head) {
if(head == null || head.next == null) return false;
ListNode p0 = head;
ListNode p1 = head.next;
while(p0 != null && p1 != null && p1.next != null) {
//这里换成==会更快 equals会慢
if(p0==p1){
return true;
}
p0 = p0.next;
p1 = p1.next.next;
}
return false;
}
}
17 / 17 个通过测试用例
状态:通过
执行用时:0 ms
内存消耗:40.2 MB
快慢指针在某一圈之后两个指针会相遇。类似于跑道上一个快的人和一个慢的人,快的会追上慢的,会相遇。