剑指offer45.把数组排成最小的数
此题用的是改进版的冒泡排序方法~
class Solution {
public String minNumber(int[] nums) {
bubbleSort(nums);
return Arrays.toString(nums).replace("[", "").replace("]", "").replace(",", "").replace(" ","");
}
public static void bubbleSort(int[] nums) {
boolean swapped = true;
int indexOfUnsortedNums = nums.length - 1; //未被交换的下标(其后都已排序)
int swapedIndex = -1; //最后一次交换的下标
while(swapped) {
swapped = false;
for(int i = 0; i < indexOfUnsortedNums; i++) {
if((""+nums[i]+nums[i+1]).compareTo(""+nums[i+1]+nums[i]) > 0) {
swap(nums,i,i+1);
swapped = true;
swapedIndex = i;
}
} indexOfUnsortedNums = swapedIndex;
}
}
public static void swap(int[] nums, int i, int j) {
//通过i,j交换nums里的值,不然你传个nums干嘛!
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
283.移动零
指路👈
还是用的冒泡法,对于是0的数,冒泡到最后一个位置。
class Solution {
public void moveZeroes(int[] nums) {
int zeroNums = 0;
for(int i = 0; i < nums.length - zeroNums; i++) {
if(nums[i] == 0) {
//把零冒泡到最后
zeroNums++;
for(int j = i; j < nums.length - zeroNums; j++){
sort(nums,j,j+1);
}
// 下一轮遍历时 i 会增加 1,但此时 nums[i] 已经和 nums[i+1] 交换了,nums[i+1] 还没有判断是否为 0,所以这里先减 1,以使下一轮继续判断 i 位置。
i--;
}
}
}
public static void sort(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
数组第k个最大值
数组第k个最大值。这题可以用选择排序,最外层循环k次即可
class Solution {
public int findKthLargest(int[] nums, int k) {
int maxIndex = 0;
for(int i = 0; i < k; i++){
maxIndex = i;
// for(int j = 0; j < nums.length - i; j++) {
for(int j = i + 1; j < nums.length; j++) {//因为k可能就是数组长度~
if(nums[maxIndex] < nums[j]) {
maxIndex = j;
}
}
swap(nums,maxIndex,i);//交换k次
}
return nums[k-1];
}
public static void swap(int[] nums, int j, int i) {
int temp = nums[j];
nums[j] = nums[i];
nums[i] = temp;
}
}
对链表进行插入排序
插入排序去操作的链表,要考虑建立什么辅助结点,其次要理解插入排序的概念。其实用到的思路就是:“我如果比你小,我就自动自觉去到前面自助找好座位。我要是比你大,那么继续往下比较的权力就落在我手上。”
class Solution {
public ListNode insertionSortList(ListNode head) {
if(head == null) return null;
//建立一个哑结点,方便处理
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
//记录已排序完成的结点
ListNode lastSorted = head;
//记录目前要和lastSorted结点比较的结点
ListNode current = head.next;
// while(current.next == null) {
while(current != null) {
if(lastSorted.val <= current.val) {
lastSorted = lastSorted.next;
} else {
//从投开始查找适合他的位置,所以要用while,且不是previousHelper.val
ListNode previousHelper = dummyHead;
while(current.val >= previousHelper.next.val) {
previousHelper = previousHelper.next;
}
//开始插入
lastSorted.next = current.next;
current.next = previousHelper.next;
previousHelper.next = current;
}
//更新需比较的结点
current = lastSorted.next;
}
return dummyHead.next;
}
}
快速排序,想到上学期的数据结构的课,画了好多快排的题但是写起代码来,真的耗费时,因为不太熟悉代码的编写~
class Solution {
public int[] sortArray(int[] nums) {
quikSort(nums,0,nums.length - 1);
return nums;
}
//快速排序的基本框架(递归实现)
public static void quikSort(int[] arr, int startIndex, int endIndex) {
//递归退出的判断条件
if(startIndex >= endIndex) {
System.out.println(arr[0]);
return;
}
//拿到分区后的锚的所在位置
int pivotIndex = partitiion(arr,startIndex,endIndex);//参数传递的是上面传过来的参数哇!
//上面分区完的左边区间的分区,不是调用分区函数啦!!!
// partitiion(arr,0,pivotIndex);这也是错的,分区范围不可能一直从0开始
// partitiion(arr,startIndex,pivotIndex);
quikSort(arr,startIndex,pivotIndex-1);
//这是右边
//quikSort(arr,pivotIndex+1,arr.length-1);这是错的,分区范围是变动的,不能一直是arr.length-1
quikSort(arr,pivotIndex+1,endIndex);
}
//分大小的具体实现
private static int partitiion(int[] arr, int startIndex, int endIndex) {
//取一个锚
// int pivot = startIndex;错啦,应该是数组对应的值,而不是下标!
int pivot = arr[startIndex];
int left = startIndex;
int right = endIndex;
//开始根据pivot分类
// while(left < right) {这个要放在里面判断
while(left != right) {
//控制right指针,至到遇到比pivot小的值
while(arr[right] > pivot && right > left) {right--;}
//控制left指针,至到遇到比pivot大的值(这里是小于等于呢)
while(arr[left] <= pivot && right > left) {left++;}
//如果还没相遇就停了,说明需要交换
if(left < right) {
swap(arr,left,right);
}
}
//pivot与两个指针重合点交换位置
swap(arr,startIndex,left);
//返回的是pivot交换后的坐标,就是left
return left;
}
//交换方法
/*private static void swap(int[] arr, int p, int q) {
/* int temp = arr[p];
arr[q] = temp;
arr[p] = arr[q];*/
//闹笑话了,交换函数写错了
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
169-多数元素
3种方法写多数元素包括摩尔计数法
class Solution {
public int majorityElement(int[] nums) {
Arrays.sort(nums);
return nums[nums.length >> 1];
}
}
合并排序数组
leetcode 合并排序数组
方法一,直接合并后排序。方法二,双指针方法,需要额外建立一个数组存放元素。方法三,逆向双指针。
class Solution {
public void merge(int[] A, int m, int[] B, int n) {
int pa = m -1; int pb = n - 1; //从后开始遍历
int tail = m+n-1;
int cur = 0;
while(pa>=0 || pb >= 0) {
if(pa==-1){
cur = B[pb--];
} else if(pb==-1){
cur = A[pa--];
} else if(A[pa]>B[pb]) {
cur = A[pa--];
} else {
cur = B[pb--];
}
A[tail--] = cur;
}
}
}
数组中的逆序对
leetcode 数组中的逆序对
这道题我学归并算法的时候已经看过一遍解析了,结果还是不会!这道题的思路是看前面有多少个比自己大的个数。那么用归并的原因是,归并具有很明显的阶段性排序。归并就是把原数组分为两组,分步用两个指针指向第一位元素。如果是从小到大的顺序排,看看哪个指针指向的数更小,就先合并。也就是我找到小的数了,那我待排序数组里还没有放入排序数组的元素不都是比我大的吗!!当然前提是两个子区间分别有序。
其次,归并的过程也是分治思想,用先调用后执行的递归来实现整个归并排序。在排序中多了一步计算逆序对的步骤。(心中要有那个图!)
class Solution {
public int reversePairs(int[] nums) {
int len = nums.length;
if(len < 2) {
return 0; //元素数组只要一个时,逆序对就是1
}
int[] copy = new int[len]; //待排序的数组
for(int i = 0;i<len;i++){
copy[i] = nums[i];
}
int[] tmp = new int[len]; //分治归并过程中的工具过程数组
return reversePairs(copy,0,len-1,tmp); //方法的重载,返回逆序对的个数
}
private int reversePairs(int[] nums,int left,int right,int[] tmp){
if(left == right) {
return 0; //只有一个元素时就可以退出递归了
}
int mid = left+(right-left)/2;
//左边区间的逆序对数
int leftPairs = reversePairs(nums,left,mid,tmp);
//右边区间的逆序对数
int rightPairs = reversePairs(nums,mid+1,right,tmp);
//如果左边最大的比右边最小的还要小,那就是直接合并的了,合并过程产生的逆序对就是0
//因为分治合并过程,左右区间都是有序的
if(nums[mid] <= nums[mid+1]) { //是小于等于噢
return leftPairs + rightPairs;
}
//合并过程产生的逆序对
int crossPairs = mergeAndCount(nums,left,mid,right,tmp);
return crossPairs+leftPairs+rightPairs; //总的逆序对
}
private int mergeAndCount(int[] nums, int left, int mid, int right, int[] tmp){ //分治法归并过程
for(int i = left; i <= right; i++){ //也是小于等于哟,不要把最后一个落下了
tmp[i]=nums[i];
}
int i = left; //左起始边界
int j = mid+1; //右起始边界
int count = 0;//计算逆序对
for(int k = left; k <= right; k++) { //必须大于等于,不然就是不稳定的归并排序
if(i==mid+1) { //i用完了,只能把右边的元素归并了
nums[k]=tmp[j];
j++;
} else if(j==right+1){ //j用完了,只能把左边的元素归并了
nums[k]=tmp[i];
i++;
} else if(tmp[i] <= tmp[j]) { //也是小于等于哟不然就有大家都是相等的情况就没有人管
//把左边小的归并了
nums[k]=tmp[i];
i++;
} else {
//把右边的归并了,并且要计算左边还剩多少个元素-->逆序对
nums[k]=tmp[j];
j++;
count+=(mid-i+1); //计算逆序对
}
}
return count;
}
}
数组的相对排序
一个计数排序的例子。
class Solution {
public int[] relativeSortArray(int[] arr1, int[] arr2) {
int upper = 0; //arr2中的最大值
for(int num : arr1) {
upper = Math.max(upper,num); //获取arr2的最小值
}
int[] frequency = new int[upper+1]; //记得加1,不然值和下标就对应不上了
//往frequency里添加数据
for(int num: arr1) {
++frequency[num];
}
//接着开始根据frequency计数排序了,先排arr2里出现的(搞清楚arr1和arr2的关系了喂!)
int[] result = new int[arr1.length];
int index = 0;
for(int num:arr2) {,
for(int i = 0; i < frequency[num]; ++i){ //Java中i++语句是需要一个临时变量取存储返回自增前的值,而++i不需要,但是两者的结果一样,++i更加优
result[index++] = num;
}
frequency[num] = 0; //清零
}
//接着排未在arr1里出现的数字
for(int j = 0; j <= upper; ++j) {
for(int i = 0;i < frequency[j];++i){
result[index++] = j;
}
}
return result;
}
}
标题164. 最大间距
大佬解析,超容易理解
方法一:为了满足现线的时间复杂度,对整个数组采用基数排序后再进行相减比较即可。
class Solution {
public int maximumGap(int[] nums) {
if(nums.length <= 1){
return 0;
}
//使用10个Arraylist装载每次基数排序的过程
List<ArrayList<Integer>> lists = new ArrayList<>();
for(int i = 0; i < 10; i++){
lists.add(new ArrayList<>());
}
int exp = 1; //先除1再除10取余,第二次除10再除10取余,第二次除100再除10取余......
int maxNum = 0; //要以最大值是否不能再取余作为基数排序进行完否的判断条件
for(int i = 0; i < nums.length; i++) {
if(maxNum < nums[i]) {
maxNum = nums[i];
}
}
//开始基数排序
while(maxNum > 0) {
//1.清空数据
for(int i = 0; i < 10; i++) {
lists.set(i,new ArrayList<Integer>());
}
//2.装载数据
for(int i = 0; i < nums.length; i++) {
int tmp = (nums[i]/exp)%10;
lists.get(tmp).add(nums[i]);
}
//3.重新拿出来
int index = 0; //数组下标
for(int i = 0; i < 10; i++) {
for(int j = 0; j < lists.get(i).size(); j++){
nums[index++] = lists.get(i).get(j);
}
}
exp *= 10;
maxNum /= 10;
}
int maxGap = 0;
//对已经排好序的数组依次计算最大间距
for(int i = 0; i < nums.length - 1; i++) {
maxGap = nums[i+1] - nums[i] > maxGap ? nums[i+1] - nums[i] : maxGap;
}
return maxGap;
}
}
使用桶排序的思路,考虑的问题就是建立多少个桶?一般用除去最大值和最小值的元素个数total-2+1个桶就行。最大最小值前或者后面没有桶。只要比total-2大就可以了明白桶里存在maxGap,桶间也可以存在gap。接着算桶内interval。
interval是桶最多能放的个数,max和min是给的数字里的最大最小值。(nums[i]-min)/interval计算得到数字应该放在哪个桶,同时要保证最后有一个桶是空的。
561.数组拆分Ⅰ
数组拆分Ⅰ
这题没有诈!直接排序即可。
class Solution {
public int arrayPairSum(int[] nums) {
Arrays.sort(nums);
int res = 0;
for(int i = 0; i < nums.length; i+=2) {
res += nums[i];
}
return res;
}
}
75.颜色分类
颜色分类
首先,这道题直接排序就可以了,问题是不用库函数,又要在原函数进行,好家伙我一开始都没想到用什么!指针啊!!单指针两次循环,双指针一次循环。
class Solution {
public void sortColors(int[] nums) {
int p0=0,p1=0;//控制0和1的两个指针
for(int i=0;i<nums.length;i++){
if(nums[i]==1) {
int tmp = nums[p1];
nums[p1] = 1;
nums[i] = tmp;
p1++;
} else if (nums[i]==0) {
int tmp = nums[p0];
nums[p0] = 0;
nums[i] = tmp;
if(p0<p1) { //保证被0换出去的1能正确地摆回1的后面
int tmp2 = nums[p1];
nums[p1] = 1;
nums[i] = tmp2;
}
p0++;
p1++;
}
}
}
}
以上是双指针的,单指针记得从0后开始就行了。
第一个只出现一次的字符
这道题的思路就是用哈希表去记录每个字符出现的频率。
class Solution {
public char firstUniqChar(String s) {
Map<Character,Integer> freqencies = new HashMap<Character,Integer>();
for(int i=0; i<s.length(); i++) {
char ch = s.charAt(i);//charAt() 方法用于返回指定索引处的字符。索引范围为从 0 到 length() - 1。
freqencies.put(ch,freqencies.getOrDefault(ch,0)+1); //getOrDefault()方法更快捷获取Map中的值,是get的功能升级版
}
for(int i=0; i<s.length(); i++) {
if(freqencies.get(s.charAt(i))==1) {
return s.charAt(i);
}
}
return ' ';
}
}
使用LinkedHashMap的方法吧,其实和方法一差不多,相当于介绍一下这种数据结构。
因为LinkedHashMap可以存储放入元素的顺序,所以先遍历一次字符串,获取到对应的字符的频率;接着遍历map,发现第一个key对应的value是1,则返回该字符。
public char firstUniqChar(String s) {
LinkedHashMap<Character, Integer> map = new LinkedHashMap<>();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
map.put(c, map.getOrDefault(c, 0) + 1);
}
for (Character c : map.keySet()) {
if (map.get(c) == 1){
return c;
}
}
return ' ';
}
类似地,下面出一题数字相关的。
只出现一次的数字
同样可以用哈希结构记录出现的频率,掌握好哈希表的遍历。这里需要用到HashMap的键和值。HashMap以键值对存储数据,其中Key-Value都是Map.Entry中的属性,故使用了以下的遍历方式。
class Solution {
public int singleNumber(int[] nums) {
Map<Integer, Integer> freq = new HashMap<Integer, Integer>();
for (int num : nums) {
freq.put(num, freq.getOrDefault(num, 0) + 1);
}
int ans = 0;
for (Map.Entry<Integer, Integer> entry : freq.entrySet()) {
int num = entry.getKey(), occ = entry.getValue();
if (occ == 1) {
ans = num;
break;
}
}
return ans;
}
}