按照出现的频率次数从高到底的顺序排列
1. (3)无重复字符的最长子串-106
使用滑动窗口来解这个问题。
注意点:remove时是移除left对应的那个字符
public int lengthOfLongestSubstring(String s) {
if ((s == null) || s.isEmpty()) {
return 0;
}
int left = 0;
int right = 0;
int maxLength = 0;
Set<Character> chars = new HashSet<>();
while (right < s.length()) {
if (!chars.contains(s.charAt(right))) {
chars.add(s.charAt(right));
right++;
maxLength = Math.max(maxLength, right - left);
} else {
chars.remove(s.charAt(left));
left++;
}
}
return maxLength;
}
2. (25)K 个一组翻转链表-84
使用一个dummy节点,再反转链表和删除等链表中比较有用,使用了一个反转部分k个链表的函数。
注意:
1. for (; (i<k) && (curr != null); i++) { 移动curr时需要注意还没有到k个curr已经为null了
2. kGroupEnd 这个是指向一组节点的末尾节点,每次反转后需要指向反转后的头节点来连接链表,然后再更新到k个反转的末尾。
3. 由于反转后startKGroup 变成了反转后的末尾节点了。
public ListNode reverseKGroup(ListNode head, int k) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode kGroupEnd = dummy;
ListNode curr = head;
while (curr != null) {
ListNode startKGroup = curr;
int i=0;
for (; (i<k) && (curr != null); i++) {
curr = curr.next;
}
if (i == k) {
kGroupEnd.next = reverseK(startKGroup, k);
kGroupEnd = startKGroup;
} else {
kGroupEnd.next = startKGroup;
}
}
return dummy.next;
}
private ListNode reverseK(ListNode head, int k) {
ListNode prev = null;
ListNode curr = head;
for (int i=0; i<k; i++) {
ListNode next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
}
3. (206)反转链表-83
方法1:迭代反转链表
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:递归反转链表
值得注意的递归退出的条件是 head==null或者head.next == null。递归返回的时候head是递归的上一次的head,所以例如 1->2->3->4。这种递归在3节点的时候已经返回, 所以返回时head是3,
head.next.next = head; 相当于实现了 4->3的转化,同时要切断3->的连接,head.next = null;其实递归实现的是尾插法的方式。
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;
}
4. (215)数组中的第K个最大元素-81
方法 1:库排序法
public int findKthLargest(int[] nums, int k) {
Arrays.sort(nums);
return nums[nums.length - k];
}
方法 2:使用优先队列(最小堆)
public int findKthLargest(int[] nums, int k) {
PriorityQueue<Integer> priQueue = new PriorityQueue<>();
for (int i=0; i<nums.length; i++) {
priQueue.add(nums[i]);
if (priQueue.size() > k) {
priQueue.poll();
}
}
return priQueue.peek();
}
方法 3:快速选择(Quickselect)
直接用快速排序不能通过,快速排序的pivot使用最右边的值,这里使用了随机值进行排序,使用右边作为枢纽元素的话,会出现又大量重复的元素时会超时。
Random random = new Random();
public int findKthLargest(int[] nums, int k) {
return quickSelect(nums, 0, nums.length -1, nums.length -k);
}
private int quickSelect(int[] nums, int left, int right, int index) {
int pivotIndex = partitionRandom(nums, left, right);
if (pivotIndex == index) {
return nums[pivotIndex];
} else if (pivotIndex < index) {
return quickSelect(nums, pivotIndex + 1, right, index);
} else {
return quickSelect(nums, left, pivotIndex -1, index);
}
}
private int partitionRandom(int[] nums, int left, int right) {
int pivotIndex = left + random.nextInt(right - left + 1);
int pivot = nums[pivotIndex];
swap(nums, pivotIndex, right);
int i = left;
for (int j=left; j<right; j++) {
if (nums[j] < pivot) {
swap(nums, i, j);
i++;
}
}
swap(nums, i, right);
return i;
}
private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
5.(15)三数之和-62
注意:
1. 需要先排序,方便去重
2. 数组遍历时的边界,固定元素时length-2,left=i+1, right = lenght -1;
3.right指针是--
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
//先对数组排序,方便去重
Arrays.sort(nums);
// 遍历数组,固定一个元素
for (int i=0; i<nums.length -2; i++) {
// 跳过重复元素
if ((i > 0) && (nums[i] == nums[i-1])) {
continue;
}
// 使用双指针查找剩余两个元素
int left = i+1;
int right = nums.length -1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum == 0) {
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
// 跳过重复的左指针元素
while ((left < right) && (nums[left] == nums[left + 1])) {
left++;
}
// 跳过重复的右指针元素
while ((left < right) && (nums[right] == nums[right - 1])) {
right--;
}
// 移动双指针
left++;
right--;
} else if (sum < 0) {
// 如果和小于0,说明需要增大和,移动左指针
left++;
} else {
// 如果和大于0,说明需要减小和,移动右指针
right--;
}
}
}
return result;
}
6.(121)买卖股票的最佳时机-61
如果当前遍历的数组不是最小价格,需要更新一次最小价格,如果比最小价格高,需要更新一次利润
public int maxProfit(int[] prices) {
int minPrice = prices[0];
int maxProfit = 0;
for (int i=1; i<prices.length; i++) {
if (minPrice > prices[i]) {
minPrice = prices[i];
} else {
maxProfit = Math.max(maxProfit, prices[i] - minPrice);
}
}
return maxProfit;
}
7.(160)相交链表-58
核心思想就是当一个链表走到结尾的时候,把另外一个链表赋值给当前链表。如果相交,那么一定会出现某个时刻2个节点相等。
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode pa = headA;
ListNode pb = headB;
while (pa != pb) {
if (pa != null) {
pa = pa.next;
} else {
pa = headB;
}
if (pb != null) {
pb = pb.next;
} else {
pb = headA;
}
}
return pa;
}
8.(1)两数之和-48
hashMap key=一个数值,value=index
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
int[] result = new int[2];
for (int i=0; i< nums.length; i++) {
if (!map.containsKey(target - nums[i])) {
map.put(nums[i], i);
} else {
int index = map.get(target - nums[i]);
result[0] = index;
result[1] = i;
}
}
return result;
}
9.(236)二叉树的最近公共祖先-45
核心思想
1. 当左右子节点都不空的时候,说明他们的上一个节点就是最小的公共祖先
2. 当有一方为空时,说明2个节点在同一个子树上
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if ((root == null) || (root == p) || (root == q)) {
return root;
}
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
if ((left != null) && (right != null)) {
return root;
}
return (left != null) ? left : right;
}
10.(53)最大子数组和-39
"最大子数组和" 是经典的算法问题,也叫做 最大子序和
这取决于 nums[i] 和 f(i−1)+nums[i] 的大小,我们希望获得一个比较大的,于是可以写出这样的动态规划转移方程:
f(i)=max{f(i−1)+nums[i],nums[i]}
public int maxSubArray(int[] nums) {
int maxSum = nums[0];
int currMax = nums[0];
for (int i=1; i< nums.length; i++) {
//动态规划转移方程:f(i)=max{f(i−1)+nums[i],nums[i]}
currMax = Math.max(nums[i], currMax + nums[i]);
maxSum = Math.max(maxSum, currMax);
}
return maxSum;
}