排序法总结
50. Pow(x, n)(快速幂)
注意:以下算法为暴力法,时间复杂度为O(n),最后结果是超时了,那么想想能不能降低以下时间复杂度。
class Solution {
public double myPow(double x, int n) {
/**
分析:
题目要求实现pow函数,最直接的思路就是暴力遍历。结果是时间复杂度超时。
*/
// return Math.pow(x,n);
// 对负数进行预处理
long b = n;
if( b < 0){
x = 1 / x;
b = -b;
}
double res = 1;
for(int i = 0; i < b; i ++){
res *= x;
}
return res;
}
}
注意:我们可以思考这样一个问题,计算2^10一般思路是2循环10次,那么可不可以使用 2 ^ 5 * 2 ^ 5呢?然后依次递归,答案肯定是可以的,那么时间复杂度就降低为O(logn)了,在实现过程了,会发现一个问题,递归过程中n是奇数或者偶数,对结果影响蛮大的,那么就要分治考虑了。
class Solution {
public double myPow(double x, int n) {
/**
分析:
题目要求实现pow函数,最直接的思路就是暴力遍历。结果是时间复杂度超时。
*/
// return Math.pow(x,n);
// 对负数进行预处理
int b = n;
if( b < 0){
x = 1 / x;
b = -b;
}
return qickPow(x,b);
}
public double qickPow(double x,int n){
// 定义递归出口
if(n == 0){
return 1.0;
}
// 定义递归函数
double y = qickPow(x,n / 2);
// 返回递归结果(这里采用分治的思想,奇数和偶数采用不同的算法)
return n % 2 == 0 ? y * y : x * y * y;
}
}
注意:上面是递归的柿子,那么可不可以使用迭代法???肯定可以,有递归就有迭代!!!
取余数 n % 2 等价于 判断二进制最右位 n & 1;
向下整除 n // 2 等价于 右移一位 n >> 1 ;
class Solution {
public double myPow(double x, int n) {
/**
分析:
题目要求实现pow函数,最直接的思路就是暴力遍历。结果是时间复杂度超时。
*/
// return Math.pow(x,n);
// 对负数进行预处理
long b = n;
if( b < 0){
x = 1 / x;
b = -b;
}
double res = 1;
while( b != 0){
if(( b & 1) == 1){
// 发现二进制末尾是奇数
res *= x;
}
x *= x;
b >>= 1;
}
return res;
}
}
56. 合并区间(排序+双指针)
class Solution {
public int[][] merge(int[][] intervals) {
/**
分析:
第一想法是先对数组排好序,然后使用双指针,按照规则进行合并。
这里其实更多考察的是api的调用,比如二维数组排序使用lambada表达式,二维数组如何截取字符串等等操作。
按照规则合并这里,是使用了一个小技巧:先判断区间,然后再合并,更新双指针,最后进行最后一位的合并判断。这个思路很有用,不要一下子把事情做完,而是分步骤。
*/
// 定义一个区间计数器
int count = 0;
// 按照第一个数字大小排序
Arrays.sort(intervals,(a,b) -> {return a[0] - b[0];});
// 初始化双指针
int start = intervals[0][0], end = intervals[0][1];
// 定义结果数组
int [][]res = new int[intervals.length][2];
// 特判
if(intervals.length == 1){
return intervals;
}
// 遍历数组
for(int i = 1; i < intervals.length; i++){
// 判定下一个区间的start是否小于上一个区间的end
if(intervals[i][0] <= end){
// 更新最大的end
end = Math.max(end,intervals[i][1]);
}else{
// 合并区间
res[count][0] = start;
res[count][1] = end;
count++;
// 双指针指向新的区间
start = intervals[i][0];
end = intervals[i][1];
}
}
// 合并最后一个数
res[count][0] = start;
res[count][1] = end;
count++;
// 截取结果数组
return Arrays.copyOfRange(res,0,count);
}
}
57. 插入区间(排序+双指针)
注意:这里的思路和上一题大体一样,就是把新的一维数组插入到原有数组中,然后排序,最后按照上一题双指针的解法。
class Solution {
public int[][] insert(int[][] intervals, int[] newInterval) {
/**
分析:
是上一题合并区间的变种题。循环遍历,按照规则插入即可!
*/
// 定义储蓄newinteveral的数组
int[][] res = new int[intervals.length + 1][2];
// 将数组复制到新数组
for(int i = 0; i < intervals.length; i++){
for(int j = 0; j < intervals[0].length; j++){
res[i][j] = intervals[i][j];
}
}
// res = Arrays.copyOfRange(intervals,0,intervals.length+1);
res[intervals.length][0] = newInterval[0];
res[intervals.length][1] = newInterval[1];
// 排序
Arrays.sort(res,(a,b) ->{return a[0] - b[0];});
// 定义计数
int count = 0;
// 定义最终数组
int[][] ret = new int[intervals.length + 1][2];
// 初始化双指针
int start = res[0][0], end = res[0][1];
// 遍历数组
for(int i = 1; i < res.length; i++){
if(res[i][0] <= end){
end = Math.max(end,res[i][1]);
}else{
ret[count][0] = start;
ret[count][1] = end;
count++;
start = res[i][0];
end = res[i][1];
}
}
ret[count][0] = start;
ret[count][1] = end;
count++;
return Arrays.copyOfRange(ret,0,count);
}
}
75. 颜色分类(冒泡排序)
class Solution {
public void sortColors(int[] nums) {
/**
分析:
题目中规定了原地排序,我们知道,这里就是手写冒泡排序了
*/
// Arrays.sort(nums);
for(int i = 0; i < nums.length - 1; i++){
for(int j = i + 1; j < nums.length; j++){
if(nums[i] > nums[j]){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
}
}
}
88. 合并两个有序数组(逆向双指针法)
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
/**
分析:
第一想法是使用双指针法.唯一同的点是本道题需要使用逆向双指针。
*/
// 定义逆向双指针
int p1 = m - 1;
int p2 = n - 1;
// 结尾指针
int index = m + n - 1;
while(p2 >= 0){
// 判断尾部谁大
if(p1 >= 0 && nums1[p1] > nums2[p2]){
// 大的数进入index下标中
nums1[index] = nums1[p1];
// 移动指针
p1--;
}else{
nums1[index] = nums2[p2];
p2--;
}
// 更新index
index--;
}
}
}
注意:另一种解法就是插入数组,然后直接调用排序api,面试官可能会diss你,不过是一个好的方法。
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
int j = 0;
for(int i = m; i < m + n; i++){
nums1[i] = nums2[j++];
}
Arrays.sort(nums1);
}
}
179. 最大数(lambada表达是和比较器)
注意:挺有意思的一道题,想要实现不同的比较排序,就要去实现比较器,java 8 可以使用lambada表达柿子。
compareable 是内部排序,也叫做自然排序,是可以自己和别人比较的,但是只能比较一次。实现的方法是compareTo方法,一般默认的类都是使用compareable(比如String和Integer)
comparator是外部排序,也叫做定制排序,可以你叫两个类型大小。实现的方法是compare方法
class Solution {
public String largestNumber(int[] nums) {
/**
分析:
首先得意识到这是一道排序题。一般而言,Arrays.sort()是可以做到升序排序的,但是不可以降序排序。
那么要实现降序排序或者对象的自定义排序,一般有两种办法,实现comparable接口或者comparator接口。
两个接口的不同点在于,comparable可以是内部排序,comparator可以是外部排序。comparable接口实现compareTo方法(比较的是自身和新的对象o),comparator接口实现的是compare方法(比较的是两个对象o)
基于以上的理解,可以用使用java 8的lambada表达式进行简化操作。
按照ansic从大到小进行排序。
*/
// 将int转换为string
String[] str = new String[nums.length];
// 储存到str中
for(int i = 0; i < nums.length; i++){
str[i] = String.valueOf(nums[i]);
}
// 排序,这里是降序排序,所以是 b + a 比较 a + b
// 这里重点学习一下lambada表达式的写法
// 传入两个参数,a 和 b,如果想升序排序 那么就是 a - b ,想降序排序就是 b - a;
// 这里的compareTo函数是String类的一个函数,用于进行字典排序,其实就是重写了compareable接口的compareTo方法
Arrays.sort(str,(a,b) -> {return (b+a).compareTo(a+b);});
// 特判
if(str[0].equals("0")){
return "0";
}
// 定义结果集
StringBuilder sb = new StringBuilder();
for(String s:str){
sb.append(s);
}
return sb.toString();
}
}
628. 三个数的最大乘积(排序+边界条件)
class Solution {
public int maximumProduct(int[] nums) {
/**
分析:
看起来很简单,实际上有很多边界条件需要判断。
先排好序。
若全是正数,那么最大乘积就是后面三个正数了。
若全是负数,那么最大乘积就是后面三个负数了。
若有正有负,那么最大乘积就是前两个负数和最后一个正数了
*/
Arrays.sort(nums);
int len = nums.length;
return Math.max(nums[0]*nums[1]*nums[len - 1],nums[len - 1]*nums[len - 2]* nums[len - 3]);
}
}
905. 按奇偶排序数组(快速排序)
class Solution {
public int[] sortArrayByParity(int[] nums) {
/**
分析:
这是一个排序问题。
解法一:按照特定的规则进行排序输出===>自定义比较器。
解法二:还可以定义一个额外数组,先储蓄偶数,再储存奇数,最后输出整个额外数组。
解法三:定义额外数组,使用双指针,偶数出储存左边,奇数储存右边
解法四:原地修改,空间复杂度为o(1),使用快排分区思想
*/
// 使用解法一
// 使用Arrays.sort(),需要传入Object类
Integer[] temp = new Integer[nums.length];
for(int i = 0; i < nums.length; i++){
temp[i] = nums[i];
}
// 排序后的返回类型是 void
Arrays.sort(temp,(a,b) -> a % 2 - b % 2);
// 将包装类的数据还给原始数组
for(int i = 0; i < nums.length; i++){
nums[i] = temp[i];
}
return nums;
}
}
使用可快速排序分区思想
class Solution {
public int[] sortArrayByParity(int[] nums) {
// 使用解法四
int p1 = 0, p2 = nums.length - 1;
while(p1 < p2){
if(nums[p1] % 2 > nums[p2] % 2){
// 奇数在前则交换
int temp = nums[p1];
nums[p1] = nums[p2];
nums[p2] = temp;
}
// 保证p1前都是偶数
if(nums[p1] % 2 == 0){
p1++;
}
// 保证p2后都是奇数
if(nums[p2] % 2 == 1){
p2--;
}
}
return nums
}
}
922. 按奇偶排序数组 II(两次遍历)
class Solution {
public int[] sortArrayByParityII(int[] nums) {
/**
分析:
在912题的基础上加了限制条件而已。
遍历两次,一次安装奇数走,一次按照偶数走
*/
int[] res = new int[nums.length];
int i = 0;
for(int num:nums){
if(num % 2 == 0){
// 是偶数
res[i] = num;
// 保证下标是偶数
i += 2;
}
}
// 重置i为1
i = 1;
for(int num:nums){
if(num % 2 == 1){
// 是偶数
res[i] = num;
// 保证下标是奇数
i += 2;
}
}
// 返回最终结果
return res;
}
}
912. 排序数组(常用的排序算法)
https://leetcode-cn.com/problems/sort-an-array/solution/fu-xi-ji-chu-pai-xu-suan-fa-java-by-liweiwei1419/
169. 多数元素(摩尔投票法)
class Solution {
public int majorityElement(int[] nums) {
/**
分析:
解法一:使用哈希表,空间复杂度为O(n),时间复杂度为O(n)
解法二:排序法:藏在管子里的蛇,身体长于管子的一半的话,砍中间就肯定能砍到,时间复杂度O(nlogn),空间复杂度o(nlogn)
解法三:摩尔投票法,分为对抗阶段和计数阶段。如果遇到相同的,就计数,不同的就对抗。count = 0,换下一个候选人,直到遍历结束
*/
// 哈希表
// int count = 0;
// Map<Integer,Integer> map = new HashMap<>();
// for(int num:nums){
// map.put(num,map.getOrDefault(num,0) + 1);
// }
// for(int key:map.keySet()){
// if(map.get(key) > nums.length / 2){
// return key;
// }
// }
// return 0;
// 排序法
// Arrays.sort(nums);
// return nums[nums.length / 2];
// 摩尔投票法
int count = 1;
// 候选人为数组第一个元素
int candidate = nums[0];
// 遍历
for(int i = 1; i < nums.length; i++){
// 计数阶段
if(candidate == nums[i]){
count++;
}else{
// 对抗阶段
count--;
if(count == 0){
// 换候选人
candidate = nums[i + 1];
}
}
}
return candidate;
}
}