我正在编写一种算法,该算法将返回具有确定的长度和求反数(数对,其中左边的数字大于右边的数字)的数组。即数组[3,1,4,2]包含三个取反(3,1),(3,2)和(4,2)。因此在实践中,当给定长度为n = 3且求反数k = 3时,该算法应生成一个数组[3、1、4、2](或满足这些要求的另一个数组)。
由于反转次数也是必须按升序对数组进行排序的交换数,因此我通过创建从1到n-1的数组并在其中使用插入排序算法来解决此问题。反向进行k次交换。
这种方法对于较小的输入也很好,但是该算法应该能够有效地生成最大为n = 10 ^ 6和k = n(n-1)/ 2的数组,并且介于两者之间,因此在O(n log n)时间而不是O(n ^ 2)中工作。下面是代码:
import java.util.*;
public class Inversions {
public int[] generate(int n, long k) {
// Don't mind these special cases
if (n == 1) {
int[] arr = {1};
return arr;
}
if (k == 0) {
int[] arr = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = 1;
}
return arr;
}
int[] arr = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = i + 1;
}
int inversions = 0;
int i = 0;
while (inversions < k && i < n) {
int j = i - 1;
while (j >= 0 && arr[j] < arr[j + 1] && inversions < k) {
int helper = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = helper;
inversions++;
j--;
}
i++;
}
return arr;
}
}
以及用于测试不同输入数组的主类:
public class Main {
public static void main(String[] args) {
Inversions in = new Inversions();
int[] arr1 = in.generate(4,3);
int[] arr2 = in.generate(4,0);
int[] arr3 = in.generate(4,6);
System.out.println(Arrays.toString(arr1)); // [3,1,4,2]
System.out.println(Arrays.toString(arr2)); // [1,1,1,1]
System.out.println(Arrays.toString(arr3)); // [4,3,2,1]
}
}
该算法不会返回与样本结果完全相同的数组,而是通过所有测试,但输入大小很大的测试除外。我还尝试了合并排序的其他变体,因为它可以在O(n log n time)下工作,但无济于事。
如果你们有一些想法,那就太好了。如果您不熟悉Java,没关系,欢迎使用伪代码或任何其他建议!
答案 0 :(得分:4)
如果反转数组中初始的 m 个元素,则会创建 m(m-1)/ 2 个反转。
如果反转初始的 m + 1 个元素,则会创建 m(m + 1)/ 2 个反转。
两者之间的差异仅为 m 。
所以:
这需要 O(n)时间,这比您所需的时间要好。
答案 1 :(得分:1)
如果k> = n-1,则将元素n-1首先放入数组中,以便将其与n-1个元素取反;否则,将其放在数组的最后,以便将其与0个元素反转。继续使用这种 greedy 方法来确定其余元素的位置。
这是一个实现了generate()的解决方案,它只需一点点数学就可以在线性时间内运行。
public class Inversions {
public static int[] generate(int n, long k) {
int[] array = new int[n];
// locate k in various sums of (n-1), (n-2), ..., 1
int a = (int) Math.sqrt((n * (n - 1) - 2 * k)); // between the sum of [(n-1)+...+(n-a)] and the sum of [(n-1)+...+(n-a-1)]
int b = n - 1 - a; // counts of (n-1), (n-2), ..., (n-a)
int c = (int) (k - n * b + (b * b + b) / 2); // spillover = k - [(n-1)+(n-b)]*b/2;
// put elements in the array
for (int i = 0; i < b; i++) {
array[i] = n - 1 - i;
}
for (int i = b; i < n - 1 - c; i++) {
array[i] = i - b;
}
array[n - 1 - c] = n - 1 - b;
for (int i = n - c; i < n; i++) {
array[i] = i - b - 1;
}
return array;
}
public static void main(String[] args) {
int n = Integer.parseInt(args[0]);
long k = Long.parseLong(args[1]);
for (int i = 0; i < n; i++) {
StdOut.print(generate(n, k)[i] + " ");
}
}
}
答案 2 :(得分:0)
另一种O(n)
算法:从排序数组开始。交换第一个和最后一个元素时,将得到x = 2 * (n-2) + 1
倒置。考虑将这两个元素固定并仅在其余阵列上工作。如果x
太大,请考虑使用较小的数组。只要需要,就重复一次。
未经测试的代码:
for (int first=0, last = n-1; remainingInversions>0; ) {
int x = 2 * (last-first-1) + 1;
if (x <= remainingInversion) {
first++;
last--;
remainingInversion -= x;
} else {
last--; // consider a smaller array
}
}
答案 3 :(得分:0)
实际上,每次将最后一个元素与之前的元素交换时,反转的数量都会增加。这是一个Java解决方案:
public static int[] generate(int n, long k) {
int[] arr = new int[n];
for(int i = 0; i < n; i++) {
arr[i] = i;
}
long inversions = 0;
int j = (n-1);
int s = 0;
while(inversions < k) {
int temp = arr[j];
arr[j] = arr[j-1];
arr[j-1] = temp;
inversions++;
j--;
if(j == s) {
j = (n-1);
s++;
}
}
return arr;
}
答案 4 :(得分:0)
我在Python中实现了具有 O(n)复杂性的实现。
它基于两个规则。
m
的数组将得到m*(m-1)/2
倒数。m
个位置,将创建m
倒置。def get_m(k):
m=0
while m*(m-1)/2<=k:
m+=1
else:
m-=1
return m
def generate(l, k):
"""
Generate array of length l with k inversions.
"""
# Generate a sorted array of length l
arr = list(range(0,l))
# If no inversions are needed, return sorted array.
if k==0:
return arr
# Find largest m such that m*(m-1)/2 <= k
m=get_m(k)
# Reverse first m elements in the array which will give m*(m-1)/2 inversions
arr = arr[m-1::-1]+arr[m:]
# Calculate for any remaining inversions
remaining_k = k-(m*(m-1)/2)
# For remaining inversions, move the last element to its left by remaining_k
if remaining_k>0:
arr.insert(int(len(arr)-remaining_k - 1), arr[-1])
arr = arr[:-1]
return arr
if __name__ == '__main__':
l = int(sys.argv[1])
k = int(sys.argv[2])
arr = generate(l, k)
print(arr)
答案 5 :(得分:0)
@zhong yang:在预期范围0 <= k <= n(n-1)/ 2上很好地工作,但是如果k超出此范围,则抛出异常或null
会更好范围而不是返回一些数组!
答案 6 :(得分:0)
有一个非常简单的方法来创建n个反转... 那就是将最后一个元素移到最前面。 由于使用了额外的内存,它的效率不高,但是我会做这样的事情:
创建一个长度为n的两倍的数组。 如果我们使用Integer []而不是int [],则从一开始到中间都填充一个前哨(即null)。 从中间填充,升序。 然后执行以下操作... 我确定我有一个错误和其他错误,但是总体思路已在下面的代码中捕获。
int start = 0;
int mid = arr.length / 2;
int end = arr.length - 1;
while (v > 0)
{
if (v < (end - mid))
{
arr[start++] = arr[mid + v];
arr[mid + v] = null;
}
else
{
arr[start++] = arr[end];
v -= (end - mid);
end--;
}
}
因此,我们有一个数组,其中填充了起始值,一堆空值,然后是原始增量值(其中一个可能已变为空值)和一个“结束”指针,该指针指向原始区域的中间。 / p>
所以最后一步是从0-> endPos(忽略空值)复制到最终数组。
答案 7 :(得分:0)
逻辑并不难。例如,我们有 10 个数字 [0,1,2,3,4,5,6,7,8,9] 来生成 18 个倒数。首先在0前插入9,--->[9,0,1,2,3,4,5,6,7,8],产生9个反转。还剩下 9 个反转,所以我们在 0 之前插入 8 个,---->[9,8,0,1,2,3,4,5,6,7],所以我们得到了额外的 8 个反转。最后,还剩 1 个反转,我们在 6---->[9,8,0,1,2,3,4,5,7,6] 之前插入 7。在这种情况下,我只使用数组。该程序以 O(n) 复杂度运行。以下代码仅考虑 n 个数字 (0,1,2.....n-1) 及其反转。
public static int[] generate(int n, long k) {
int[] a = new int[n];
int[] b = new int[n];
for (int i = 1; i < n; i++) {
a[i] = 1 + a[i - 1];
}
if (n == 0 || k == 0) return a;
else {
int i = 0;
while (k > 0) {
if (k > n - i - 1) {
b[i] = a[n - 1 - i];
}
else {
//auxilary array c to store value
int[] c = new int[(int) (k + 1)];
for (int j = i; j < n - 1 - k; j++) {
b[j] = j - i;
}
for (int j = (int) (n - 1 - k); j < n; j++) {
c[j - (int) (n - 1 - k)] = j - i;
}
b[(int) (n - 1 - k)] = c[(int) k];
for (int j = (int) (n - k); j < n; j++) {
b[j] = c[j - (int) (n - k)];
}
break;
}
k = k - (n - 1 - i);
i++;
}
return b;
}
}