【*M】leetcode-75. 颜色分类

package com.leetcode.medium;

import java.util.Arrays;

/*
 * 给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地对它们进行排序,
 * 使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
 * 我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。必须在不使用库的sort函数的情况下解决这个问题。
 * 示例 1:
 * 
 * 输入:nums = [2,0,2,1,1,0]
 * 输出:[0,0,1,1,2,2]
 * 
 * 示例 2:
 * 
 * 输入:nums = [2,0,1]
 * 输出:[0,1,2]
 * 
 * 
 * 提示:
 * 
 *     n == nums.length
 *     1 <= n <= 300
 *     nums[i] 为 0、1 或 2
 * 
 * 
 * 进阶:
 * 
 *     你可以不使用代码库中的排序函数来解决这道题吗?
 *     你能想出一个仅使用常数空间的一趟扫描算法吗?
 *     来源:力扣(LeetCode)
 *链接:https://leetcode.cn/problems/sort-colors
 *著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
 */
public class Hot075_sortColors {

	// -----------------------------------Solution1:单指针----------------------------------
	/*
	 * 方法一:单指针
	 * 
	 * 思路与算法
	 * 
	 * 我们可以考虑对数组进行两次遍历。在第一次遍历中,我们将数组中所有的 0 交换到数组的头部。在第二次遍历中,我们将数组中所有的 1 交换到头部的 0
	 * 之后。此时,所有的 2 都出现在数组的尾部,这样我们就完成了排序。
	 * 
	 * 具体地,我们使用一个指针 ptr 表示「头部」的范围,ptr 中存储了一个整数,表示数组 nums 从位置 0 到位置 ptr−1 都属于「头部」。ptr
	 * 的初始值为 0,表示还没有数处于「头部」。
	 * 
	 * 在第一次遍历中,我们从左向右遍历整个数组,如果找到了 0,那么就需要将 0
	 * 与「头部」位置的元素进行交换,并将「头部」向后扩充一个位置。在遍历结束之后,所有的 0 都被交换到「头部」的范围,并且「头部」只包含 0。
	 * 
	 * 在第二次遍历中,我们从「头部」开始,从左向右遍历整个数组,如果找到了 1,那么就需要将 1
	 * 与「头部」位置的元素进行交换,并将「头部」向后扩充一个位置。在遍历结束之后,所有的 1 都被交换到「头部」的范围,并且都在 0 之后,此时 2
	 * 只出现在「头部」之外的位置,因此排序完成。
	 * 
	 * 时间复杂度:O(n),其中 n 是数组 nums 的长度。 空间复杂度:O(1)。
	 */
	public void sortColors1(int[] nums) {
		int ptr = 0;
		for (int i = 0; i < nums.length; i++)
			if (nums[i] == 0)
				swap(nums, i, ptr++);
		for (int i = ptr; i < nums.length; i++)
			if (nums[i] == 1)
				swap(nums, i, ptr++);
	}

	// -----------------------------------Solution2:双指针----------------------------------
	/*
	 * 方法一需要进行两次遍历,那么我们是否可以仅使用一次遍历呢?我们可以额外使用一个指针,即使用两个指针分别用来交换 0 和 1。
	 * 
	 * 具体地,我们用指针 p0 来交换 0,p1​ 来交换 1,初始值都为 0。当我们从左向右遍历整个数组时:
	 * 
	 * 如果找到了 1,那么将其与 nums[p1]进行交换,并将 p1向后移动一个位置,这与方法一是相同的;
	 * 
	 * 如果找到了 0,那么将其与 nums 进行交换,并将 p0​ 向后移动一个位置。这样做是正确的吗?我们可以注意到,因为连续的 0 之后是连续的
	 * 1,因此如果我们将 0 与 nums[p0] 进行交换,那么我们可能会把一个 1 交换出去。当 p0​<p1​ 时,我们已经将一些 1 连续地放在头部,
	 * 此时一定会把一个 1 交换出去,导致答案错误。因此,如果 p0<p1​,那么我们需要再将 nums[i] 与 nums[p1] 进行交换,其中
	 * i是当前遍历到的位置,在进行了第一次交换后,nums[i] 的值为 1,我们需要将这个 1 放到「头部」的末端。在最后,无论是否有
	 * p0<p1​,我们需要将 p0​ 和 p1​ 均向后移动一个位置,而不是仅将 p0 向后移动一个位置。
	 * 
	 * 时间复杂度:O(n),其中 n 是数组 nums 的长度。 空间复杂度:O(1)。
	 */
	public void sortColors2(int[] nums) {
		int p0 = 0, p1 = 0;
		for (int i = 0; i < nums.length; ++i) {
			if (nums[i] == 1)
				swap(nums, i, p1++);
			else if (nums[i] == 0) {
				swap(nums, i, p0);
				if (p0 < p1) {
					swap(nums, i, p1);
				}
				++p0;
				++p1;
			}
		}
	}

	// -----------------------------------Solution3:双指针----------------------------------
	/*
	 * 与方法二类似,我们也可以考虑使用指针 p0​ 来交换 0,p2 来交换 2。此时,p0 的初始值仍然为 0,而 p2​ 的初始值为
	 * n−1。在遍历的过程中,我们需要找出所有的 0 交换至数组的头部,并且找出所有的 2 交换至数组的尾部。 由于此时其中一个指针 p2
	 * 是从右向左移动的,因此当我们在从左向右遍历整个数组时,如果遍历到的位置超过了 p2,那么就可以直接停止遍历了。
	 * 具体地,我们从左向右遍历整个数组,设当前遍历到的位置为 i,对应的元素为 nums[i];
	 * 
	 * 如果找到了 0,那么与前面两种方法类似,将其与 nums[p0] 进行交换,并将 p0 向后移动一个位置; 如果找到了 2,那么将其与 nums[p2]
	 * 进行交换,并将 p2​ 向前移动一个位置。
	 * 
	 * 这样做是正确的吗?可以发现,对于第二种情况,当我们将 nums[i] 与 nums[p2] 进行交换之后,新的 nums[i] 可能仍然是 2,也可能是
	 * 0。然而此时我们已经结束了交换,开始遍历下一个元素 nums[i+1],不会再考虑 nums[i] 了,这样我们就会得到错误的答案。
	 * 
	 * 因此,当我们找到 2 时,我们需要不断地将其与 nums[p2] 进行交换,直到新的 nums[i] 不为 2。此时,如果 nums[i] 为
	 * 0,那么对应着第一种情况;如果 nums[i] 为 1,那么就不需要进行任何后续的操作。 时间复杂度:O(n),其中 n 是数组 nums 的长度。
	 * 空间复杂度:O(1)。
	 */
	public void sortColors3(int[] nums) {
		int p0 = 0, p2 = nums.length - 1;
		for (int i = 0; i < p2; ++i) {
			while (i <= p2 && nums[i] == 2)
				swap(nums, i, p2--);
			if (nums[i] == 0)
				swap(nums, i, p0++);
		}
	}

	public void sortColors4(int[] arr) {
		int L = -1, R = arr.length;
		for (int i = 0; i < R;) {
			if (arr[i] < 1)
				swap(arr, i, 1 + L++);
			else if (arr[i] > 1)
				swap(arr, i--, R-- - 1);
			i++;
		}
	}

	// -------------------------swap--------------------------------
	private void swap(int[] arr, int i, int j) {
		int temp = arr[i];
		arr[i] = arr[j];
		arr[j] = temp;
	}

	// -------------------------main--------------------------------
	public static void main(String[] args) {
		int[] arr = { 0, 1, 1, 2, 2, 0, 1, 0, 2, 1, 1, 0, 1 };
		Hot075_sortColors instance = new Hot075_sortColors();
		instance.sortColors3(arr);
		Arrays.stream(arr).forEach(System.out::println);
		//
		int testTime = 500000;
		int maxSize = 100;
		int maxValue = 2;
		boolean succeed = true;
		for (int i = 0; i < testTime; i++) {
			int[] arr1 = toPositiveNumber(generateRandomArray(maxSize, maxValue));
			int[] arr2 = copyArray(arr1);
			instance.sortColors2(arr1);
			Arrays.sort(arr2);
			if (!isEqual(arr1, arr2)) {
				// 打印arr1
				System.out.println(Arrays.toString(arr1));
				// 打印arr2
				System.out.println(Arrays.toString(arr2));
				succeed = false;
				break;
			}
		}
		System.out.println(succeed ? "Nice!" : "Fucking!");
	}

	// -----------------------------------for-test----------------------------------
	// for test
	public static int[] generateRandomArray(int maxSize, int maxValue) {
		// Math.random() -> [0,1) 所有的小数,等概率返回一个
		// Math.random() * N -> [0,N) 所有的小数,等概率返回一个
		// (int)Math.random() -> [0,N-1] 所有的整数,等概率返回一个
		int[] arr = new int[(int) ((maxSize + 1) * Math.random())]; // 长度随机
		for (int i = 0; i < arr.length; i++) {
			arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
		}
		return arr;
	}

	// for test
	public static int[] copyArray(int[] arr) {
		if (arr == null) {
			return null;
		}
		int[] res = new int[arr.length];
		for (int i = 0; i < arr.length; i++) {
			res[i] = arr[i];
		}
		return res;
	}
	
	public static int[] toPositiveNumber(int[] arr) {
        for (int i = 0; i < arr.length; i++) {
            arr[i] = arr[i] > 0 ? arr[i] : -arr[i];
        }
        return arr;
    }

	// for test
	public static boolean isEqual(int[] arr1, int[] arr2) {
		if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
			return false;
		}
		if (arr1 == null && arr2 == null) {
			return true;
		}
		if (arr1.length != arr2.length) {
			return false;
		}
		for (int i = 0; i < arr1.length; i++) {
			if (arr1[i] != arr2[i]) {
				return false;
			}
		}
		return true;
	}

	// for test
	public static void printArray(int[] arr) {
		if (arr == null) {
			return;
		}
		for (int i = 0; i < arr.length; i++) {
			System.out.println(arr[i] + " ");
		}
		System.out.println();
	}
}

### 回答1: 好的,我来用中文回复这个链接:https://leetcode-cn.com/tag/dynamic-programming/ 这个链接是 LeetCode 上关于动态规划的题目集合。动态规划是一种常用的算法思想,可以用来解决很多实际问题,比如最长公共子序列、背包问题、最短路径等等。在 LeetCode 上,动态规划也是一个非常重要的题型,很多题目都需要用到动态规划的思想来解决。 这个链接里包含了很多关于动态规划的题目,按照难度从简单到困难排列。每个题目都有详细的题目描述、输入输出样例、题目解析和代码实现等内容,非常适合想要学习动态规划算法的人来练习和提高自己的能力。 总之,这个链接是一个非常好的学习动态规划算法的资源,建议大家多多利用。 ### 回答2: 动态规划是一种算法思想,通常用于优化具有重叠子问题和最优子结构性质的问题。由于其成熟的数学理论和强大的实用效果,动态规划在计算机科学、数学、经济学、管理学等领域均有重要应用。 在计算机科学领域,动态规划常用于解决最优化问题,如背包问题、图像处理、语音识别、自然语言处理等。同时,在计算机网络和分布式系统中,动态规划也广泛应用于各种优化算法中,如链路优化、路由算法、网络流量控制等。 对于算法领域的程序员而言,动态规划是一种必要的技能和知识点。在LeetCode这样的程序员平台上,题目分类和标签设置十分细致和方便,方便程序员查找并深入学习不同类型的算法LeetCode动态规划标签下的题目涵盖了各种难度级别和场景的问题。从简单的斐波那契数列、迷宫问题到可以用于实际应用的背包问题、最长公共子序列等,难度不断递进且话题丰富,有助于开发人员掌握动态规划的实际应用技能和抽象思维模式。 因此,深入LeetCode动态规划分类下的题目学习和练习,对于程序员的职业发展和技能提升有着重要的意义。 ### 回答3: 动态规划是一种常见的算法思想,它通过将问题拆分成子问题的方式进行求解。在LeetCode中,动态规划标签涵盖了众多经典和优美的算法问题,例如斐波那契数列、矩阵链乘法、背包问题等。 动态规划的核心思想是“记忆化搜索”,即将中间状态保存下来,避免重复计算。通常情况下,我们会使用一张二维表来记录状态转移过程中的中间值,例如动态规划求解斐波那契数列问题时,就可以定义一个二维数组f[i][j],代表第i项斐波那契数列中,第j个元素的值。 在LeetCode中,动态规划标签下有众多难度不同的问题。例如,经典的“爬楼梯”问题,要求我们计算到n级楼梯的方案数。这个问题的解法非常简单,只需要维护一个长度为n的数组,记录到达每一级楼梯的方案数即可。类似的问题还有“零钱兑换”、“乘积最大子数组”、“通配符匹配”等,它们都采用了类似的动态规划思想,通过拆分问题、保存中间状态来求解问题。 需要注意的是,动态规划算法并不是万能的,它虽然可以处理众多经典问题,但在某些场景下并不适用。例如,某些问题的状态转移过程比较复杂,或者状态转移方程中存在多个参数,这些情况下使用动态规划算法可能会变得比较麻烦。此外,动态规划算法也存在一些常见误区,例如错用贪心思想、未考虑边界情况等。 总之,掌握动态规划算法对于LeetCode的学习和解题都非常重要。除了刷题以外,我们还可以通过阅读经典的动态规划书籍,例如《算法竞赛进阶指南》、《算法与数据结构基础》等,来深入理解这种算法思想。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值