系列汇总:《刷题系列汇总》
文章目录
——————《剑指offeer》———————
JZ30. 连续子数组的最大和
- 题目描述:输入一个整型数组,数组里有正数也有负数。数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。要求时间复杂度为
O(n)
- 优秀思路:核心点在于理解到:若前面部分的和 < 0,说明前面的部分对于计算总和这件事来说只会拖后腿,那么便舍弃掉,将计算当前sum的起点重设为0。但同时要一直更新当前的最大和的值
public class Solution {
public int FindGreatestSumOfSubArray(int[] array) {
int maxSum = Integer.MIN_VALUE; // 因为有负数,所以不能设为0
int sum = 0;
for(int i = 0;i < array.length;i++){
sum += array[i];
maxSum = Math.max(maxSum,sum);
if(sum < 0) sum = 0; // 核心!!若小于0,则计算和的起点重新置为0
}
return maxSum;
}
}
JZ26. 二叉搜索树与双向链表(需重写)
- 题目描述:输入一棵二叉搜索树
(空 || 左<根 && 右>根)
,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。 - 优秀思路:用list存储中序遍历的节点,根据list构建节点间的连接
import java.util.ArrayList;
public class Solution {
public TreeNode Convert(TreeNode pRootOfTree) {
if (pRootOfTree == null) return null;
ArrayList<TreeNode> list = new ArrayList<>(); //存储中序遍历的节点
tree2List(list,pRootOfTree);
return list2SortTree(list);
}
// 用list存储中序遍历的节点:这个递归很妙
public void tree2List(ArrayList<TreeNode> list,TreeNode root){
if (root != null){
tree2List(list,root.left);
list.add(root);
tree2List(list,root.right);
}
}
// 根据list构建节点间的连接
public TreeNode list2SortTree(ArrayList<TreeNode> list){
TreeNode head = list.get(0); // 最左边角落的节点
TreeNode cur = head;
for (int i = 1;i < list.size();++i){
TreeNode next = list.get(i);
next.left = cur;
cur.right = next;
cur = next; // 更新当前节点
}
return head;
}
}
JZ29. 最小的K个数
- 题目描述:给定一个数组,找出其中最小的
K
个数。例如数组元素是4,5,1,6,2,7,3,8
这8
个数字,则最小的4个数字是1,2,3,4
。如果K>
数组的长度,那么返回一个空的数组 - 优秀思路(代码自编写):快速排序,读取前K个即可
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int[] input, int k) {
ArrayList<Integer> res = new ArrayList<>();
if(k > input.length) return res;
quickSort(input,0,input.length-1);
for(int i = 0;i < k;i++){
res.add(input[i]);
}
return res;
}
// 快速排序法
private void quickSort(int[] input,int left,int right){
if(left >= right) return;
int tempLeft = left;
int tempRight = right;
int refer = input[left];// 中心轴
// 这是左边left空出
// 从最右边开始移动,让最右边也空出来
int flag = 1;
while(left <= right){
if(left == right){
input[left] = refer;
break;
}
if(flag == 1){ // 移动右边
if(input[right] <= refer){
input[left] = input[right];
left++;
flag = 2;
}else right--;
}
if(flag == 2){ // 移动左边
if(input[left] <= refer) left++;
else{
input[right] = input[left];
right--;
flag = 1;
}
}
}
quickSort(input,tempLeft,left-1);
quickSort(input,left+1,tempRight);
}
}
——————《LeectCode》———————
53. 最大子序和(同上JZ30,略)
23. 合并K个升序链表
- 题目描述:给你一个链表数组,每个链表都已经按升序排列。请你将所有链表合并到一个升序链表中,返回合并后的链表。
- 优秀思路:
- 优秀思路1:【优先级队列+归并排序】利用了优先级队列自动排序的特性。
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
if(lists.length == 0) return null;
if(lists.length == 1) return lists[0];
Queue<ListNode> q = new PriorityQueue<>((node1,node2) -> node1.val - node2.val);
// 新建链表存储结果
ListNode res = new ListNode(0);
ListNode index = res;
for(ListNode head:lists){// 因为是升序数组,最小值一定在三个头结点中
if(head != null) q.add(head);
}
// 下面的排序方式相当于归并排序
while(!q.isEmpty()){
ListNode minNode = q.poll(); // 最小节点
// 下面这一步隐含的意思是,若个ListNode被遍历完了,则自动跳过了,无需其他处理
if(minNode.next != null) q.add(minNode.next);
index.next = minNode;
index = index.next; // 更新索引
}
return res.next;
}
}
- 优秀思路2:【分治】将所有ListNode两两分治合并。
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
if(lists.length == 0) return null;
if(lists.length == 1) return lists[0];
return merge(lists,0,lists.length-1);
}
// 分治
private ListNode merge(ListNode[] lists,int left,int right){
if(left == right) return lists[left]; // 此处不是返回lists[0]
int mid = (left + right) >> 1;
return merge2List(merge(lists,left,mid),merge(lists,mid+1,right));
}
// 合并两列表
private ListNode merge2List(ListNode node1,ListNode node2){
ListNode res = new ListNode(0);
ListNode index = res;
while(node1 != null && node2 != null){
if(node1.val <= node2.val){
index.next = node1;
node1 = node1.next;
}else{
index.next = node2;
node2 = node2.next;
}
index = index.next;
}
index.next = (node1==null? node2:node1);
return res.next;
}
}
4. 寻找两个正序数组的中位数
- 题目描述:给定两个大小分别为
m
和n
的正序(从小到大)数组nums1
和nums2
。请你找出并返回这两个正序数组的 中位数 。 - 我的思路:【归并排序】归并排序合并两数组,再根据合并数组的奇偶长度输出不同值
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int len = nums1.length + nums2.length;
int[] all = merge2num(nums1, nums2);
if(len % 2 == 0) return (double) (all[len/2]+all[len/2-1])/2;
else return (double) all[len/2];
}
// 归并排序——合并两数组
private int[] merge2num(int[] nums1, int[] nums2){
int len1 = nums1.length;
int len2 = nums2.length;
int[] res = new int[len1+len2];
int i = 0;
int j = 0;
int count = 0;
while(i<len1 && j<len2){
if(nums1[i] <= nums2[j]){
res[count++] = nums1[i];
i++;
}else{
res[count++] = nums2[j];
j++;
}
}
// 贴上没统计完的
if(i < len1) for(i = i;i<len1;i++) res[count++] = nums1[i];
if(j < len2) for(j = j;j<len2;j++) res[count++] = nums2[j];
return res;
}
}
- 我的优秀思路: 无需合并数组,找到索引 len/2 和 len/2-1 处的值即可
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
// 无需合并数组,找到索引 len/2 和 len/2-1 处的值即可
int len1 = nums1.length,len2 = nums2.length;
int len = len1 + len2;
int value1 = 0;
int value2 = 0;
int i = 0;
int j = 0;
int count = 0;
int flag = 0;
while(i<len1 && j<len2){
if(nums1[i] <= nums2[j]){
if(count == len/2){
value1 = nums1[i];
flag = 1;
break;
}
if(count == len/2 - 1){
value2 = nums1[i];
}
count++;
i++;
}else{
if(count == len/2){
value1 = nums2[j];
flag = 1;
break;
}
if(count == len/2 - 1){
value2 = nums2[j];
}
count++;
j++;
}
}
if(flag == 0){
if(i < len1){
for(i = i;i<len1;i++){
if(count == len/2){
value1 = nums1[i];
break;
}
if(count == len/2 - 1){
value2 = nums1[i];
}
count++;
}
}
if(j < len2){
for(j = j;j<len2;j++){
if(count == len/2){
value1 = nums2[j];
break;
}
if(count == len/2 - 1){
value2 = nums2[j];
}
count++;
}
}
}
if(len % 2 == 0) return (double) (value1 + value2)/2;
else return (double) value1;
}
}
- 优秀思路:二分查找,文中要求复杂度达到
O(log(m+n))
,故考虑二分查找 -
/* 主要思路:要找到第 k (k>1) 小的元素,那么就取 pivot1 = nums1[k/2-1] 和 pivot2 = nums2[k/2-1] 进行比较 * 这里的 "/" 表示整除 * nums1 中小于等于 pivot1 的元素有 nums1[0 .. k/2-2] 共计 k/2-1 个 * nums2 中小于等于 pivot2 的元素有 nums2[0 .. k/2-2] 共计 k/2-1 个 * 取 pivot = min(pivot1, pivot2),两个数组中小于等于 pivot 的元素共计不会超过 (k/2-1) + (k/2-1) <= k-2 个 * 这样 pivot 本身最大也只能是第 k-1 小的元素 * 如果 pivot = pivot1,那么 nums1[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums1 数组 * 如果 pivot = pivot2,那么 nums2[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums2 数组 * 由于我们 "删除" 了一些元素(这些元素都比第 k 小的元素要小),因此需要修改 k 的值,减去删除的数的个数 */
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int length1 = nums1.length, length2 = nums2.length;
int totalLength = length1 + length2;
if (totalLength % 2 == 1) {
int midIndex = totalLength / 2;
double median = getKthElement(nums1, nums2, midIndex + 1);
return median;
} else {
int midIndex1 = totalLength / 2 - 1, midIndex2 = totalLength / 2;
double median = (getKthElement(nums1, nums2, midIndex1 + 1) + getKthElement(nums1, nums2, midIndex2 + 1)) / 2.0;
return median;
}
}
public int getKthElement(int[] nums1, int[] nums2, int k) {
int length1 = nums1.length, length2 = nums2.length;
int index1 = 0, index2 = 0;
int kthElement = 0;
while (true) {
// 边界情况
if (index1 == length1) {
return nums2[index2 + k - 1];
}
if (index2 == length2) {
return nums1[index1 + k - 1];
}
if (k == 1) {
return Math.min(nums1[index1], nums2[index2]);
}
// 正常情况
int half = k / 2;
int newIndex1 = Math.min(index1 + half, length1) - 1;
int newIndex2 = Math.min(index2 + half, length2) - 1;
int pivot1 = nums1[newIndex1], pivot2 = nums2[newIndex2];
if (pivot1 <= pivot2) {
k -= (newIndex1 - index1 + 1);
index1 = newIndex1 + 1;
} else {
k -= (newIndex2 - index2 + 1);
index2 = newIndex2 + 1;
}
}
}
}
240. 搜索二维矩阵 II
- 题目描述:编写一个高效的算法来搜索
m x n
矩阵matrix
中的一个目标值target
。该矩阵具有以下特性:- 每行的元素从左到右升序排列。
- 每列的元素从上到下升序排列。
- 我的思路(优秀):【dfs】从左下角开始搜索
class Solution {
boolean flag = false;
public boolean searchMatrix(int[][] matrix, int target) {
if(matrix.length == 0 || matrix[0].length == 0) return false;
search(matrix,matrix.length-1,0,target);
return flag;
}
private void search(int[][] matrix,int i,int j,int target){
if(i < 0 || j >= matrix[0].length) return;
if(matrix[i][j] == target){
flag = true;
return;
}
if(matrix[i][j] > target) search(matrix,i-1,j,target);
if(matrix[i][j] < target) search(matrix,i,j+1,target);
}
}
- 更优代码:dfs会增加无效路径开销
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
if(matrix.length == 0 || matrix[0].length == 0) return false;
int i = matrix.length-1;
int j = 0;
while(i >= 0 && j < matrix[0].length){
if(matrix[i][j] == target) return true;
else if(matrix[i][j] < target) j++;
else i--;
}
return false;
}
}
- 优秀思路:二分查找
395. 至少有 K 个重复字符的最长子串
- 题目描述:给你一个字符串
s
和一个整数k
,请你找出s
中的最长子串, 要求该子串中的每一字符出现次数都不少于k
。返回这一子串的长度。 - 我的思路(7% - 5%):递归地根据当前字串的第一个出现次数小于k的字母将字串分为两段处理
class Solution {
int maxLen = 0;
public int longestSubstring(String s, int k) {
if(k==1) return s.length();
dfs(s, 0, s.length() - 1, k);
return maxLen;
}
private void dfs(String s, int left, int right, int k) {
// 边界条件:该子串长度<k
if(right - left + 1 < k) return;
// 统计每次字幕出现的次数
int[] count = new int[26];
for(int i = left;i <= right;i++) count[s.charAt(i)-'a']++;
// 找到的第一个出现字数<k的字母
int split = -1;
for(int i = left;i <= right;i++){
if(count[s.charAt(i)-'a'] > 0 && count[s.charAt(i)-'a'] < k){
split = i;
break;
}
}
// 根据该字母将s分为两段
if(split == -1){ // 不存在该字母
maxLen = Math.max(maxLen,right-left+1);
return;
}else{
if(split == left) dfs(s,left+1,right,k); // 该字母出现在开头
else if(split == right){ // 该字母出现在结尾
maxLen = Math.max(maxLen,right-left);
return;
}else{ // 该字母出现在中间
dfs(s,left,split-1,k);
dfs(s,split+1,right,k);
}
}
}
}
- 我的思路改进(8% - 5%):上思路每次都得重新统计字母次数,效率很低,事实上每次找出的所有出现字数<k的位置都能用上
class Solution {
int maxLen = 0;
public int longestSubstring(String s, int k) {
if(k==1) return s.length();
dfs(s, 0, s.length() - 1, k);
return maxLen;
}
private void dfs(String s, int left, int right, int k) {
// 边界条件:该子串长度<k
if(right - left + 1 < k) return;
// 统计每次字幕出现的次数
int[] count = new int[26];
for(int i = left;i <= right;i++) count[s.charAt(i)-'a']++;
// 找到的第一个出现字数<k的字母
int preSplit = left;
int index = preSplit;
int split = -1;
while(index <= right){
if(right - preSplit + 1 < k) return;
for(int i = preSplit;i <= right;i++){
index++;
if(count[s.charAt(i)-'a'] > 0 && count[s.charAt(i)-'a'] < k){
split = i;
break;
}
}
// 根据该字母将s分为两段
if(split == -1){ // 不存在该字母
maxLen = Math.max(maxLen,right-preSplit+1);
return;
}
// 最后一个分割点
else if(preSplit == split + 1){
dfs(s,preSplit,right,k);// 取出最后一段
return;
}else{
if(split == left){ // 该字母出现在开头
dfs(s,preSplit+1,right,k); // 取出最后一段
return;
}else if(split == right){ // 该字母出现在结尾
dfs(s,preSplit,split-1,k);
return;
}else{ // 该字母出现在中间,划分两区域
dfs(s,preSplit,split-1,k);
preSplit = split+1;
index = preSplit;
continue;
}
}
}
return;
}
}
- 优秀思路(我的):递归+分治
- 1.确定每个字母出现的次数
- 2.以出现次数在(0,k)之间的字母作为子串分割点,分别取出各子串(最后一段单独取出)分别判断
- 3.递归上过程,若某子串内无分割点,用其长度更新maxLen(题目要求的输出)
class Solution {
int maxLen = 0; // 输出结果
public int longestSubstring(String s, int k) {
if(k == 1) return s.length();
dfs(s, 0, s.length() - 1, k);
return maxLen;
}
private void dfs(String s, int left, int right, int k) {
// 边界条件:子串长度 < k
if(right - left + 1 < k) return;
// 统计每次字幕出现的次数
int[] count = new int[26];
for(int i = left;i <= right;i++) count[s.charAt(i)-'a']++;
// 根据出现字数小于k的字母当做分割点,逐段递归
int preSplit = left; // 上一个分割点
int split = -1;
for(int i = left;i <= right;i++){
if(count[s.charAt(i)-'a'] > 0 && count[s.charAt(i)-'a'] < k){
split = i;
dfs(s,preSplit,split-1,k);
preSplit = split+1; // 更新
}
}
if(split == -1){ // 子串内无分割点
maxLen = Math.max(maxLen,right-left+1);
}else{ // 统计子串的最后一段
dfs(s,preSplit,right,k);
}
}
}
169. 多数元素
- 题目描述:给定一个大小为
n
的数组,找到其中的多数元素。多数元素是指在数组中出现次数 大于⌊ n/2 ⌋
的元素。你可以假设数组是非空的,并且给定的数组总是存在多数元素。 - 优秀思路:【投票计数法】见代码
class Solution {
public int majorityElement(int[] nums) {
int candidate = 0; //备选人
int vote = 0; // 备选人的票数
for(int i = 0;i < nums.length;i++) {
if(vote == 0) candidate = nums[i]; // 当前无备选人/上一位备选人被淘汰,暂时确定当前位为备选人
// 读票环节
if(nums[i] == candidate) vote++;
else vote--;
}
return candidate;
}
}
493. 翻转对(需重写)
-
题目描述:给定一个数组
nums
,如果i < j
且nums[i] > 2*nums[j]
我们就将(i, j)
称作一个重要翻转对。你需要返回给定数组中的重要翻转对的数量。
-
优秀思路:在归并排序的过程中,假设对于数组
nums[l..r]
而言,我们已经分别求出了子数组nums[l..m]
与nums[m+1..r]
的翻转对数目,并已将两个子数组分别排好序,则nums[l..r]
中的翻转对数目,就等于两个子数组的翻转对数目之和,加上左右端点分别位于两个子数组的翻转对数目
。举个例子,对于 1 3 2 3 1,先分为1 3 2 和 3 1,count = 0 + 1 + count( 1 2 3 | 1 3) = 0 + 1 + 1 = 2
class Solution {
int count = 0; // 重要翻转对数量
public int reversePairs(int[] nums) {
if(nums == null || nums.length < 2) return 0;
mergeSort(nums, 0, nums.length - 1);
return count;
}
// 归并排序
private void mergeSort(int[] nums, int start, int end) {
if(start == end) return;
int mid = start + (end - start) / 2;
mergeSort(nums, start, mid);
mergeSort(nums, mid + 1, end);
int i = start;
int j = mid + 1;
// 以中间为界限寻找重要翻转对
// 注意:须排序前统计翻转对,否则数量就不对了
while(i <= mid && j <= end) {
if((long) nums[i] > 2 * (long) nums[j]) { // long时为了避免数字过大,int存不下
count += mid - i + 1;
j++; // 找到,后一段右移
}else {
i++; // 未找到,前一段后移
}
}
// 注意统计完当前子数组的count后,需要进行排序,由于是void,排序后nums自动返回
int[] tempArr = new int[end - start + 1]; // 排序后矩阵
i = start;
j = mid + 1;
int idx = 0;
while(i <= mid && j <= end) {
tempArr[idx++] = nums[i] < nums[j] ? nums[i++] : nums[j++];
}
// 放置未排序部分
while(i <= mid) tempArr[idx++] = nums[i++];
while(j <= end) tempArr[idx++] = nums[j++];
// 更新nums为排序好的矩阵
for(i = 0, j = start; j <= end; i++, j++) {
nums[j] = tempArr[i];
}
}
}
315. 计算右侧小于当前元素的个数(没看懂)
-
题目描述:给定一个整数数组
nums
,按要求返回一个新数组counts
。数组counts
有该性质:counts[i]
的值是nums[i]
右侧小于nums[i]
的元素的数量。
-
优秀思路:利用归并排序
class Solution {
private int[] index; // 存储排序数组元素的原索引
private int[] temp;
private int[] tempIndex;
private int[] ans; //
public List<Integer> countSmaller(int[] nums) {
index = new int[nums.length];
temp = new int[nums.length];
tempIndex = new int[nums.length];
ans = new int[nums.length];
for (int i = 0; i < nums.length; ++i) {
index[i] = i; // 索引数组
}
int l = 0, r = nums.length - 1;
mergeSort(nums, l, r);
List<Integer> list = new ArrayList<Integer>();
for (int num : ans) {
list.add(num);
}
return list;
}
public void mergeSort(int[] a, int l, int r) {
if (l >= r) {
return;
}
int mid = (l + r) >> 1;
mergeSort(a, l, mid);
mergeSort(a, mid + 1, r);
merge(a, l, mid, r);
}
public void merge(int[] a, int l, int mid, int r) {
int i = l, j = mid + 1, p = l;
while (i <= mid && j <= r) {
if (a[i] <= a[j]) {
temp[p] = a[i];
tempIndex[p] = index[i];
ans[index[i]] += (j - mid - 1);
++i;
++p;
} else {
temp[p] = a[j];
tempIndex[p] = index[j];
++j;
++p;
}
}
while (i <= mid) {
temp[p] = a[i];
tempIndex[p] = index[i];
ans[index[i]] += (j - mid - 1);
++i;
++p;
}
while (j <= r) {
temp[p] = a[j];
tempIndex[p] = index[j];
++j;
++p;
}
for (int k = l; k <= r; ++k) {
index[k] = tempIndex[k];
a[k] = temp[k];
}
}
}