Lecode 算法。
链接:https://leetcode.cn
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
1.(53) 最大子数组和
题目:
解题思路:
考虑数组中的元素是单独成一段还是加入之前的那一段,故比较该元素于前一段组合大小,取大者即可。
java代码如下所示:
** java求最大值Math.max**
2.(1)两数之和
题目:
解题思路:
(1)暴力破解
注意:(1)nums长度为nums.length
(2)return 数组为return new int[]{i,j}
3(88)合并两个有序数组
题目:
解题方法:
(1)使用sort内置函数
**注:Arrays.sort()为内置Array函数
(2)双指针
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
int p1 = 0, p2 = 0;
int[] sorted = new int[m + n];
int cur;
while (p1 < m || p2 < n) {
if (p1 == m) {
cur = nums2[p2++];
} else if (p2 == n) {
cur = nums1[p1++];
} else if (nums1[p1] < nums2[p2]) {
cur = nums1[p1++];
} else {
cur = nums2[p2++];
}
sorted[p1 + p2 - 1] = cur;
}
for (int i = 0; i != m + n; ++i) {
nums1[i] = sorted[i];
}
}
}
(4)(350)两个数组的交集II
题目:
解题思路:
(1)哈希表
由于同一个数字在两个数组中都可能出现多次,因此需要用哈希表存储每个数字出现的次数。对于一个数字,其在交集中出现的次数等于该数字在两个数组中出现次数的最小值。
代码如下所示:
class Solution {
public int[] intersect(int[] nums1, int[] nums2) {
// 挑选出两个数组找个短的那个
if (nums1.length > nums2.length) {
return intersect(nums2, nums1);
}
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
// nums1 = [4,9,5], nums2 = [9,4,9,8,4]
for (int num : nums1) {
// map.getOrDefault : 存在这个数就返回,不存在就返回默认值
int count = map.getOrDefault(num, 0) + 1;
map.put(num, count);
}
// 开辟一块内存空间用来存放两个数组的交集
int[] intersection = new int[nums1.length];
int index = 0;
for (int num : nums2) {
// num1中不存在这个数就在map中添加num=0
int count = map.getOrDefault(num, 0);
// 存在这个数就往后执行
if (count > 0) {
// 把这个数填充到数组中
intersection[index++] = num;
// 计数减一
count--;
// 如果还大于0
if (count > 0) {
// 再次添加进去,覆盖之前那个key
map.put(num, count);
} else {
// 不大于0移除这个数
map.remove(num);
}
}
}
// public static int[] copyOfRange(int[] original, int from, int to)
// 对已有([9, 4, 0])的数组进行截取和赋值,结果为[9,4]
return Arrays.copyOfRange(intersection, 0, index);
}
}
注意:(1)Map的用法:Map.getOrDefault(key,默认值);
Map中会存储一一对应的key和value。
如果 在Map中存在key,则返回key所对应的的value。
如果 在Map中不存在key,则返回默认值。
(2)Arrays的用法:Arrays.copyOfRange(T[] original, int from, int to)复制from到to的元素。
(5)(121)买卖股票的最佳时机
题目为:
解题思路:
(1)暴力破解:会超时;
(2)一次遍历:记录当前遍历最小值,如果当前值小于最小值,替换最小值,如何不小于,计算差值后于最大利润比较,若大于,则替换最大利润。
代码如下所示:
public class Solution {
public int maxProfit(int prices[]) {
int minprice = Integer.MAX_VALUE;# Integer.MAX_VALUE表示int数据
类型的最大取值数:2 147 483 647
Integer.MIN_VALUE表示int数据类型的最小取值数:-2 147 483 648
int maxprofit = 0;
for (int i = 0; i < prices.length; i++) {
if (prices[i] < minprice) {
minprice = prices[i];
} else if (prices[i] - minprice > maxprofit) {
maxprofit = prices[i] - minprice;
}
}
return maxprofit;
}
}
注意: Integer.MAX_VALUE表示int数据类型的最大取值数:2 147 483 647
Integer.MIN_VALUE表示int数据类型的最小取值数:-2 147 483 648
(6)(118)杨辉三角
题目如下所示:
解题思路:
代码如下:
class Solution {
public List<List<Integer>> generate(int numRows) {
List<List<Integer>> ret = new ArrayList<List<Integer>>();
for (int i = 0; i < numRows; ++i) {
List<Integer> row = new ArrayList<Integer>();
for (int j = 0; j <= i; ++j) {
if (j == 0 || j == i) {
row.add(1);
} else {
row.add(ret.get(i - 1).get(j - 1)
+ ret.get(i - 1).get(j));//将上一级的数进行两两相加
}
}
ret.add(row);
}
return ret;
}
}
(7)36.有效的数独
题目如下所示:
解题思路:
用哈希表记录每一行、每一列、每一个小九宫格中,每个数字出现的次数。后判断对应条件即可。代码如下所示:
class Solution {
public boolean isValidSudoku(char[][] board) {
int[][] rows = new int[9][9];
int[][] columns = new int[9][9];
int[][][] subboxes = new int[3][3][9];
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
char c = board[i][j];
if (c != '.') {
int index = c - '0' - 1; // 字符转int,计算该c为多少,ASCII码计算
rows[i][index]++;
columns[j][index]++;
subboxes[i / 3][j / 3][index]++; // 判别为第几个方块里出现的数字。
if (rows[i][index] > 1 || columns[j][index] > 1 || subboxes[i / 3][j / 3][index] > 1) {
return false;
}
}
}
}
return true;
}
}
(8)73.矩阵置零
题目如下所示:
注:原地算法:一个原地算法(in-place algorithm)是一种使用小的,固定数量的额外之空间来转换资料的算法。当算法执行时,输入的资料通常会被要输出的部分覆盖掉。
解题思路:
(1)普通标记数组,占用O(M+N)
设定行标和列标,当有0,置1.后更新全部。
代码如下所示:
class Solution {
public void setZeroes(int[][] matrix) {
int m = matrix.length, n = matrix[0].length;
boolean[] row = new boolean[m];
boolean[] col = new boolean[n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (matrix[i][j] == 0) {
row[i] = col[j] = true;
}
}
}
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (row[i] || col[j]) {
matrix[i][j] = 0;
}
}
}
}
}
(2)使用两个标记变量
其第一行第一列用于判断剩余行列是否存在0;
代码如下所示:
class Solution {
public void setZeroes(int[][] matrix) {
int m = matrix.length, n = matrix[0].length;
boolean flagCol0 = false, flagRow0 = false;
for (int i = 0; i < m; i++) {
if (matrix[i][0] == 0) {
flagCol0 = true;
}
}
for (int j = 0; j < n; j++) {
if (matrix[0][j] == 0) {
flagRow0 = true;
}
}
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
if (matrix[i][j] == 0) {
matrix[i][0] = matrix[0][j] = 0;
}
}
}
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
if (matrix[i][0] == 0 || matrix[0][j] == 0) {
matrix[i][j] = 0;
}
}
}
if (flagCol0) {
for (int i = 0; i < m; i++) {
matrix[i][0] = 0;
}
}
if (flagRow0) {
for (int j = 0; j < n; j++) {
matrix[0][j] = 0;
}
}
}
}
(9) 387.字符串中的第一个唯一字符
题目如下所示:
解题思路:
(1)使用哈希表存储频数
代码如下所示:
class Solution {
public int firstUniqChar(String s) {
Map<Character, Integer> frequency = new HashMap<Character, Integer>();
for (int i = 0; i < s.length(); ++i) {
char ch = s.charAt(i);
frequency.put(ch, frequency.getOrDefault(ch, 0) + 1);
}
for (int i = 0; i < s.length(); ++i) {
if (frequency.get(s.charAt(i)) == 1) {
return i;
}
}
return -1;
}
}
注意:String中操作charAt(),此方法返回这个字符串的指定索引处的char值
(10)383 赎金信
题目如下所示:
解题思路:进行字符统计,使用Map或者int[26] 进行相关的统计。代码如下所示:
class Solution {
public boolean canConstruct(String ransomNote, String magazine) {
Map<Character,Integer> map=new HashMap<Character,Integer>();
for(int i=0;i<ransomNote.length();i++){
char a=ransomNote.charAt(i);
map.put(a,map.getOrDefault(a,0)+1);
}
for(int i=0;i<magazine.length();i++){
char b=magazine.charAt(i);
map.put(b,map.getOrDefault(b,0)-1);
}
for (Map.Entry<Character, Integer> entry : map.entrySet()) {
int pos = entry.getValue();
if(pos>0){
return false;
}
}
return true;
}
}
也可以如下所示:
public boolean canConstruct(String ransomNote, String magazine) {
int[] arr = new int[26];
for (int i = 0; i < magazine.length(); i++)
arr[magazine.charAt(i) - 'a']++;
for (int i = 0; i < ransomNote.length(); i++)
if (arr[ransomNote.charAt(i) - 'a']-- == 0) return false;
return true;
}
注:(1)map的用法:java中获取map中key和value的方式有两种:
map.keySet() : 先获取map中的key,然后根据key获取value。
map.entrySet() : 获取map中的key和value,只需查询一次。
ToCharArray( )的用法,将字符串对象中的字符转换为一个字符数组char[]。
(2)题目为小写字母,故可以分配26个空间来进行相关字符的计数实现。
(11)141 环形链表
题目如下所示:
解题思路:
(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) {
Set<ListNode> a=new HashSet<ListNode>();
while(head!=null){
if(!a.add(head)){
return true;
}
head=head.next;
}
return false;
}
}
(2)Floyd 判圈算法(龟兔赛跑算法)
围绕成一个圈,双指针,分别按不同速率前进,若最终兔子能超过乌龟,则说明有圈。
代码如下所示:
public class Solution {
public boolean hasCycle(ListNode head) {
if (head == null || head.next == null) { //防止初始后序为空
return false;
}
ListNode slow = head;
ListNode fast = head.next;
while (slow != fast) {
if (fast == null || fast.next == null) {
return false;
}
slow = slow.next;
fast = fast.next.next;
}
return true;
}
}
注:(1)|和||的差别:
||:短路或。当符号"||"左边程序为真(true)后。结果就为true。不去运算符号右边程序。如果左边程序为false,运算右边程序,右边程序为true,结果为true。符号左右两边有一个为true,结果为true。
|:符号左右程序都会运算。两边都为true,结果为true。两边有一个为false,则结果为false;符号左右两边有一个为true,结果为true。
(2)定义链表ListNode时,
- 链表的首个值不能为0,当首个参数为0时,代表着链表为空。 只需要定义一个ListNode xx = newListNode(0);即可。即只定义一个空链表。 不需要定义长度 。
- 赋值时 通过xx.next = newListNode(4);来赋值,注意此时是赋值给下一个指针指向的位置,此时此链表一个值,值为4。
- 取第一个值时,只需要xx.val即可。 取第二或之后的值时,需要xx = xx.next;int x = xx.val;这个方式取值。
(12)21.合并两个有序链表
题目如下所示:
解题思路:
(1)递归
两个链表头部值较小的与另一个剩下元素操作结果合并。
代码如下所示:
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if (l1 == null) {
return l2;
} else if (l2 == null) {
return l1;
} else if (l1.val < l2.val) {
l1.next = mergeTwoLists(l1.next, l2);
return l1;
} else {
l2.next = mergeTwoLists(l1, l2.next);
return l2;
}
}
}
(2)迭代
设置哨兵结点,比较l1和l2,进行合并。
代码如下所示:
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode prehead = new ListNode(-1);
ListNode prev = prehead;
while (l1 != null && l2 != null) {
if (l1.val <= l2.val) {
prev.next = l1;
l1 = l1.next;
} else {
prev.next = l2;
l2 = l2.next;
}
prev = prev.next;
}
// 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可
prev.next = l1 == null ? l2 : l1;
return prehead.next;
}
}
注意:创建链表哨兵头可以为:ListNode pre = new ListNode(-1);
ListNode prehead = pre;
(13)移除链表元素
题目如下所示:
解题思路:
(1)递归
递归的终止条件是 head 为空,此时直接返回 head。当 head 不为空时,递归地进行删除操作,然后判断 head 的节点值是否等于 val并决定是否要删除 head。
代码如下所示:
class Solution {
public ListNode removeElements(ListNode head, int val) {
if (head == null) {
return head;
}
head.next = removeElements(head.next, val);
return head.val == val ? head.next : head;
}
}
(2)迭代
由于链表的头节点 head 有可能需要被删除,因此创建哑节点 dummyHead,令 dummyHead.next=head,初始化 temp=dummyHead,然后遍历链表进行删除操作。最终返回 dummyHead.next 即为删除操作后的头节点。
代码如下所示:
class Solution {
public ListNode removeElements(ListNode head, int val) {
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
ListNode temp = dummyHead;
while (temp.next != null) {
if (temp.next.val == val) {
temp.next = temp.next.next;
} else {
temp = temp.next;
}
}
return dummyHead.next;
}
}
(14)206反转链表
题目如下所示:
解题思路为:
(1)迭代
在遍历链表时,将当前节点的 next指针改为指向前一个节点。由于节点没有引用其前一个节点,因此必须事先存储其前一个节点。在更改引用之前,还需要存储后一个节点。最后返回新的头引用。
代码如下所示:
class Solution {
public ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode curr = head;
while (curr != null) {
ListNode next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
}
}
(2)递归
n k.next.next=nk
需要注意的是 n1的下一个节点必须指向 ∅。如果忽略了这一点,链表中可能会产生环。
代码如下所示:
class Solution {
public ListNode reverseList(ListNode head) {
if(head==null||head.next==null){
return head;
}
ListNode newhead=reverseList(head.next);
head.next.next=head;//加环
head.next=null;//删环,防止循环
return newhead;
}
}
(15)83删除排序链表中的重复元素
题目如下所示:
解题思路如下:
一次遍历:
由于给定的链表是排好序的,因此重复的元素在链表中出现的位置是连续的,因此我们只需要对链表进行一次遍历,就可以删除重复的元素。
代码如下所示:
class Solution {
public ListNode deleteDuplicates(ListNode head) {
ListNode result=head;
while(head!=null&&head.next!=null){
if(head.val==head.next.val){
head.next=head.next.next;
}else{
head=head.next;
}
}
return result;
}
}
(16)有效的括号
题目如下所示:
解题思路:
使用栈这一数据结构来解决。
我们遍历给定的字符串 sss。当我们遇到一个左括号时,我们会期望在后续的遍历中,有一个相同类型的右括号将其闭合。由于后遇到的左括号要先闭合,因此我们可以将这个左括号放入栈顶。
代码如下所示:
class Solution {
public boolean isValid(String s) {
int n = s.length();
if (n % 2 == 1) {
return false;
}
Map<Character, Character> pairs = new HashMap<Character, Character>() {{
put(')', '(');
put(']', '[');
put('}', '{');
}};
Deque<Character> stack = new LinkedList<Character>();
for (int i = 0; i < n; i++) {
char ch = s.charAt(i);
if (pairs.containsKey(ch)) {
if (stack.isEmpty() || stack.peek() != pairs.get(ch)) {
return false;
}
stack.pop();
} else {
stack.push(ch);
}
}
return stack.isEmpty();
}
}
注:(1)Java中Stack栈中的 peek()方法 和 pop()方法 的区别
stack.peek() 的作用是返回栈顶元素,但不是将栈顶元素弹出。
stack.pop() 的作用是返回栈底元素,并且同时将栈顶元素出栈。
(2)新建一个栈:Deque stack = new LinkedList();
从官方解释来看,ArrayDeque是无初始容量的双端队列,LinkedList则是双向链表。
(17)232 用栈实现队列
题目如下所示:
解题思路:
将一个栈当作输入栈,用于压入 push传入的数据;另一个栈当作输出栈,用于 pop 和 peek操作。
每次 pop 或 peek 时,若输出栈为空则将输入栈的全部数据依次弹出并压入输出栈,这样输出栈从栈顶往栈底的顺序就是队列从队首往队尾的顺序。
代码如下所示:
class MyQueue {
Deque<Integer> inStack;
Deque<Integer> outStack;
public MyQueue() {
inStack = new ArrayDeque<Integer>();
outStack = new ArrayDeque<Integer>();
}
public void push(int x) {
inStack.push(x);
}
public int pop() {
if (outStack.isEmpty()) {
in2out();
}
return outStack.pop();
}
public int peek() {
if (outStack.isEmpty()) {
in2out();
}
return outStack.peek();
}
public boolean empty() {
return inStack.isEmpty() && outStack.isEmpty();
}
private void in2out() {
while (!inStack.isEmpty()) {
outStack.push(inStack.pop());
}
}
}
注:
(18)二叉树的前序遍历
题目如下所示:
解题思路:
(1)递归
可以显而易见,可以使用递归的方法进行解决。
代码如下所示:
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
preorder(root, res);
return res;
}
public void preorder(TreeNode root, List<Integer> res) {
if (root == null) {
return;
}
res.add(root.val);
preorder(root.left, res);
preorder(root.right, res);
}
}
(2)迭代
代码如下所示:
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
if (root == null) {
return res;
}
Deque<TreeNode> stack = new LinkedList<TreeNode>();
TreeNode node = root;
while (!stack.isEmpty() || node != null) {
while (node != null) {
res.add(node.val);
stack.push(node);
node = node.left;
}
node = stack.pop();
node = node.right;
}
return res;
}
}
(19)二叉树的后序遍历
题目如下所示:
解题思路:
(1)递归
(2)迭代(难)
代码如下所示:
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
if (root == null) {
return res;
}
Deque<TreeNode> stack = new LinkedList<TreeNode>();
TreeNode prev = null;
while (root != null || !stack.isEmpty()) {
while (root != null) {
stack.push(root);
root = root.left;
}
root = stack.pop();
if (root.right == null || root.right == prev) {
res.add(root.val);
prev = root; //用于辨别是否重复添加,防止循环
root = null; // 防止重复增添左结点
} else {
stack.push(root);
root = root.right;
}
}
return res;
}
}
(20)二叉树的层序遍历
题目如下所示:
解题思路:
(1)BFS(广度优先算法)
广度优先遍历是按层层推进的方式,遍历每一层的节点。
代码如下所示:
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> ret = new ArrayList<List<Integer>>();
if (root == null) {
return ret;
}
Queue<TreeNode> queue = new LinkedList<TreeNode>();
queue.offer(root);
while (!queue.isEmpty()) {
List<Integer> level = new ArrayList<Integer>();
int currentLevelSize = queue.size();
for (int i = 1; i <= currentLevelSize; ++i) {
TreeNode node = queue.poll();
level.add(node.val);
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
ret.add(level);
}
return ret;
}
}
(2)DFS(深度优先算法)
递归的思想。
代码如下所示:
import java.util.*;
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
if(root==null) {
return new ArrayList<List<Integer>>();
}
//用来存放最终结果
List<List<Integer>> res = new ArrayList<List<Integer>>();
dfs(1,root,res);
return res;
}
void dfs(int index,TreeNode root, List<List<Integer>> res) {
//假设res是[ [1],[2,3] ], index是3,就再插入一个空list放到res中
if(res.size()<index) {
res.add(new ArrayList<Integer>());
}
//将当前节点的值加入到res中,index代表当前层,假设index是3,节点值是99
//res是[ [1],[2,3] [4] ],加入后res就变为 [ [1],[2,3] [4,99] ]
res.get(index-1).add(root.val);
//递归的处理左子树,右子树,同时将层数index+1
if(root.left!=null) {
dfs(index+1, root.left, res);
}
if(root.right!=null) {
dfs(index+1, root.right, res);
}
}
}
注:
1、add()和offer()区别:
add()和offer()都是向队列中添加一个元素。一些队列有大小限制,因此如果想在一个满的队列中加入一个新项,调用 add() 方法就会抛出一个 unchecked 异常,而调用 offer() 方法会返回 false。因此就可以在程序中进行有效的判断!
2、poll()和remove()区别:
remove() 和 poll() 方法都是从队列中删除第一个元素。如果队列元素为空,调用remove() 的行为与 Collection 接口的版本相似会抛出异常,但是新的 poll() 方法在用空集合调用时只是返回 null。因此新的方法更适合容易出现异常条件的情况。