- 写于2019年6月9日
文章目录
- [139. 单词拆分](https://leetcode.com/problems/word-break/)
- [141. 环形链表](https://leetcode.com/problems/linked-list-cycle/)
- [155. 最小栈](https://leetcode.com/problems/min-stack/)
- [160. 相交链表](https://leetcode.com/problems/intersection-of-two-linked-lists/)
- [169. 求众数](https://leetcode.com/problems/majority-element/)
- [198. 打家劫舍](https://leetcode.com/problems/house-robber/)
- [206. 反转链表](https://leetcode.com/problems/reverse-linked-list/)
139. 单词拆分
① 题目描述
- 给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
- 说明:
拆分时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。 - 示例 1:
输入: s = “leetcode”, wordDict = [“leet”, “code”]
输出: true
解释: 返回 true,因为 “leetcode” 可以被拆分成 “leet code”。
- 示例 2:
输入: s = “applepenapple”, wordDict = [“apple”, “pen”]
输出: true 解释: 返回true, 因为 “applepenapple” 可以被拆分成 “apple pen apple”。
注意你可以重复使用字典中的单词。
- 示例 3:
输入: s = “catsandog”, wordDict = [“cats”, “dog”, “sand”, “and”, “cat”]
输出: false
② 动态规划
- 设
dp[i]
表示长度为i的字符串,通过分割后是否在wordDict中。 - 使用
j
对长度为i的字符串进行分割,得到两个子串,如果dp[j]
为true,表示子串[0, j - 1]
在wordDict中;于是还需要判断剩下的子串[j, i - 1]
是否在wordDict中。 - 上面的两个条件都满足,更新
dp[i] = true
,并且无需使用j继续分割字符串。 - 代码如下:
public boolean wordBreak(String s, List<String> wordDict) {
boolean[] dp = new boolean[s.length() + 1];//dp[i]表示长度为i的字符串,分割后是否在wordDict中
dp[0] = true;
for (int i = 1; i <= s.length(); i++) {
for (int j = 0; j < i; j++) {// 通过j分割字符串,当dp[j]为true时,看subString(j,i)是否在wordDict中
String sub = s.substring(j, i);
if (dp[j] && wordDict.contains(sub)){// 不仅要j之前的子串在在wordDict中,还要求从j开始的子串也要在在wordDict中
dp[i] = true;
break;// 无需继续遍历下去
}
}
}
return dp[s.length()];
}
141. 环形链表
① 题目描述
- 给定一个链表,判断链表中是否有环。
- 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是
-1,则在该链表中没有环。 - 示例 1:
- 示例 2:
- 示例 3:
② hash表
- 使用
HashMap
存储已经访问过的节点,如果链表存在环,则某一节点的下一节点已经被访问过;不存在环,会一直遍历到null节点
(尾节点的下一节点)。 - 很奇怪的一点: 自己最开始想的很简单,根据他的题目描述,想着他会给pos。通过判断pos是否为-1,就可以知道他是否存在环。但实际的数据中,竟然没有pos,搞得自己一下子就蒙了,无从下手。
- 代码如下:
public boolean hasCycle(ListNode head) {
HashMap<ListNode, Integer> map = new HashMap<>();
while (head != null) {
if (!map.containsKey(head)) {
map.put(head, 1);
head = head.next;
} else {
return true;// 已经访问过的节点,说明存在环,直接返回true
}
}
return false;
}
③ 追及问题(双指针)
- 使用slow和fas指针,如果不存在环,就是普通的直线追及问题,fast指针比slow指针先到达终点;如果存在环,进入环以后就成了圆圈中的追及问题,slow指针会在下一圈遇上fast指针。
- 不存在环,如果节点数为偶数,fast的next节点将为null;如果节点为奇数,fast指向的节点为null。此时都表明没有环,直接返回
false
。 - 为了让fast比slow跑的快,每次fas向前移动2个节点,slow只移动一个节点。
- 代码如下,花费了
0ms
:
public boolean hasCycle(ListNode head) {
if (head == null) {
return false;
}
ListNode slow = head;// slow跑一步
ListNode fast = head.next;// fast跑两步
while (slow != fast) {
if (fast == null || fast.next == null) {// 无环:单数节点,fast指向null;双数节点,fast的next节点为null
return false;
}
slow = slow.next;
fast = fast.next.next;
}
return true;
}
155. 最小栈
① 题目描述
- 设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。
- push(x) – 将元素 x 推入栈中。
- pop() – 删除栈顶的元素。
- top() – 获取栈顶元素。
- getMin() – 检索栈中的最小元素。
- 示例:
② 自己的想法(Stack+List)
- 使用stack的
push
、pop
、peek
实现对应的push、pop、top
功能。 - 使用List存储stack中的值,push时向List中添加元素,pop时删除List中对应的元素,getMin时遍历List数组,获得最小元素。
- 代码如下,花费
93ms
:
public class MinStack {
List<Integer> value;
Stack<Integer> stack;
/**
* initialize your data structure here.
*/
public MinStack() {
value=new ArrayList<>();
stack = new Stack<>();
}
public void push(int x) {
stack.push(x);
value.add(x);
}
public void pop() {
Integer top=stack.pop();
value.remove(top);
}
public int top() {
return stack.peek();
}
public int getMin() {
int min=Integer.MAX_VALUE;
for (int i=0;i<value.size();i++){
if (min>value.get(i)){
min=value.get(i);
}
}
return min;
}
}
- 后来经过两个栈的启发:
List.get(i)
存储stack中有i+1
个元素时的最小元素。代码修改之后的运行时间47ms
:
public class MinStack {
List<Integer> value;
Stack<Integer> stack;
public MinStack() {
value = new ArrayList<>();
stack = new Stack<>();
}
public void push(int x) {
stack.push(x);
if (value.size() == 0) {
value.add(x);
} else {
value.add(Math.min(value.get(value.size() - 1), x));// value[i]记录stack中有i个元素时的最小值
}
}
public void pop() {
Integer top = stack.pop();
value.remove(value.size() - 1);
}
public int top() {
return stack.peek();
}
public int getMin() {
return value.get(value.size() - 1);
}
}
③ 使用2个栈
- 一个栈用于存储所有元素,一个用于存储前者对应的最小元素。
- 代码如下,运行时间
48ms
:
public class MinStack {
Stack<Integer> stack1;
Stack<Integer> stack2;
public MinStack() {
stack1 = new Stack<>();
stack2 = new Stack<>();
}
public void push(int x) {
stack1.push(x);
if (stack2.isEmpty()) {
stack2.push(x);
} else {
stack2.push(Math.min(stack2.peek(), x));
}
}
public void pop() {
stack1.pop();
stack2.pop();
}
public int top() {
return stack1.peek();
}
public int getMin() {
return stack2.peek();
}
}
160. 相交链表
① 题目描述
- 编写一个程序,找到两个单链表相交的起始节点。
- 如下面的两个链表:
在节点 c1 开始相交。 - 示例1:
输出:Reference of the node with value = 8
- 示例2:
输出:Reference of the node with value = 2
- 示例3:
输出:null
② 使用HasMap
- 先遍历headA,使用
HasMap
存储headA中的所有节点。再遍历headB,然后看HasMap
中是否已经存在。 - 代码如下,运行时间
8ms
:
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
HashMap<ListNode, Integer> hashMap = new HashMap<>();
while (headA != null) {
hashMap.put(headA, 1);
headA = headA.next;
}
while (headB != null) {
if (hashMap.containsKey(headB)) {
return headB;
}
headB = headB.next;
}
return null;
}
③ 从相同的位置开始走
- 基于这样一个观察:如果
headA
和headB
有交点,则相交后的公共部分长度相等。
① 先获取headA
和headB
的长度
② 更新headA
或者headB
的指向,使得二者剩余长度与较短者一致。
③ 判断节点是否相等,从而知道是否有公共节点。 - 代码如下,运行时间
1ms
:
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
int len1 = 0, len2 = 0;
ListNode p = headA;
ListNode q = headB;
while (p != null) {
len1++;
p = p.next;
}
while (q != null) {
len2++;
q = q.next;
}
if (len1 > len2) {// 更新p或q的指向,使得headA和headB中剩下节点数一致
while (len1 != len2) {
headA = headA.next;
len1--;
}
} else {
while (len1 != len2) {
headB = headB.next;
len2--;
}
}
while (headA != null) {
if (headA == headB) {
return headA;
}
headA = headA.next;
headB = headB.next;
}
return null;
}
④ 多走一个链表,使两个链表到达相等位置时走过的是相同的距离
headA
将经过X1、y、X2,最终到达公共节点;headB
将经过X2、y、X1,终到达公共节点。- 二者到达公共节点时,所走的距离相同。
- 如果没有公共节点,
headA
将经过X1、X2,最终到达headB
的null节点;headB
将经过将经过X2、X1,最终到达headA
的null节点。
- 代码如下,运行时间
1ms
:
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode p = headA;
ListNode q = headB;
while (p != q) {// 要么都达到一圈以后的末尾,要么都达到交点
p = (p != null) ? p.next : headB;
q = (q != null) ? q.next : headA;
}
return p;
}
169. 求众数
① 题目描述
- 给定一个大小为 n 的数组,找到其中的众数。众数是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。
- 你可以假设数组是非空的,并且给定的数组总是存在众数。
- 示例 1:
输入: [3,2,3]
输出: 3
- 示例 2:
输入: [2,2,1,1,1,2,2]
输出: 2
② 自己的想法:使用hashMap
- 使用HashMap存储每个元素的个数,key为元素,value为其个数。value大于总数一半的元素为众数。
- 代码如下,运行时间
10ms
:
public int majorityElement(int[] nums) {
HashMap<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
map.put(nums[i], map.getOrDefault(nums[i], 0) + 1);
}
for (Integer key : map.keySet()) {
if (map.get(key) >= (nums.length / 2 + 1)) {// 判断个数是否大于一半
return key;// 返回对应key即元素值
}
}
return -1;
}
③ 排序后的中位数就是众数
- 通过观察发现:由于众数大于总个数的一半,将数组排序后中位数就是众数。
- 代码如下,运行时间
1ms
:
public int majorityElement(int[] nums) {
Arrays.sort(nums);
return nums[nums.length / 2];
}
198. 打家劫舍
① 题目描述
- 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
- 给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
- 示例 1:
输入: [1,2,3,1]
输出: 4
解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
- 示例 2:
输入: [2,7,9,3,1]
输出: 12
解释: 偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。
② 自己的想法:动态规划
- dp[i]表示打劫至第i家时,可以获得的最高金额。有两种情况:可以选择打劫第i家,则上一家是
i-2
;不打劫第i家,上一家是i-1
。即dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]);
- 特殊情况:打劫第1家时,
dp[i] = Math.max(dp[i - 1], nums[i]);
。要么打劫第0家,要么打劫第1家。 - 要注意: 街道上,一户人家都没有的特殊情况。
- 代码如下,运行时间
0ms
:
public int rob(int[] nums) {
if (nums.length==0){
return 0;
}
int[] dp = new int[nums.length];// dp[i]表示打劫至第i家时,所获得的最大钱财数
dp[0] = nums[0];
for (int i = 1; i < nums.length; i++) {
if (i - 2 >= 0) {
dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]);
} else {
dp[i] = Math.max(dp[i - 1], nums[i]);
}
}
return dp[nums.length-1];
}
206. 反转链表
① 题目描述
- 反转一个单链表。
- 示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
- 进阶:
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?
② 自己的想法:头插法
- 新开辟一个链表,遍历原来的链表,构造新的结点使用头插法加入到新的链表中。
- 时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( n ) O(n) O(n),因为需要新开辟一个链表。
- 代码如下,运行时间
0ms
:
public ListNode reverseList(ListNode head) {
ListNode p = null;
while (head != null) {
ListNode temp = new ListNode(head.val);
temp.next = p;
p = temp;
head = head.next;
}
return p;
}
③ 原地迭代
- 将
1->2->3->4->5->NULL
借用指针变成null <- 1 <- 2 <- 3 <- 4 <- 5
- 需要使用pre指针记录cur指针所应指向的前一节点,更改cur使
cur.next=pre
,然后更新pre和cur指针的指向。**注意:**在更改cur的指向前,一定要使用多余的指针保留其原始的next结点。 - 时间复杂度: O ( n ) O(n) O(n),空间复杂度: O ( 1 ) O(1) O(1)。
- 代码如下,运行时间
0ms
:
public ListNode reverseList(ListNode head) {
ListNode pre=null;
ListNode cur=head;
while (cur!=null){
ListNode temp=cur.next;
cur.next=pre;
pre=cur;
cur=temp;
}
return pre;
}