1. 程式人生 > >二維陣列中的最長遞減子序列

二維陣列中的最長遞減子序列

給定一個如下的二維陣列a[][]

1   3   5   7   4

2   1   8   6   5

4   0  -1  -2   6

求其中的最長遞減子序列:7, 5, 3, 1, 0, -1, -2,長度為7。子序列只能朝向上下左右四個方向,不能朝對角線方向。

思路:

該題一看感覺可以用動態規劃做,但是下標不確定從哪裡開始算起,因為有上下左右四個方向,沒有辦法順序計算。只有用遞迴的方法來做。用一個函式FindMax(i, j)來表示以該位置起始的最長遞減子序列的長度,那麼只要取FindMax(i-1, j)、FindMax(i+1, j)、FindMax(i, j-1)、FindMax(i, j+1)中的最大值,然後加1即可,當然前提是上下左右的元素比(i, j)的元素小。

程式碼如下:

#include <iostream>
#include <assert.h>
using namespace std;

int Max(int a, int b, int c, int d)
{
	a = (a > b) ? a : b;
	a = (a > c) ? a : c;
	return (a > d) ? a : d;
}

int FindMax(int a[], int res[], bool flag[], int m, int n, int i, int j)
{
	int umax = 0, dmax = 0, lmax = 0, rmax = 0;
	//當前元素的下標
	int cur = i * n + j;

	//上面的元素
	int up = (i - 1) * n + j;
	//如果存在上面的元素,並且當前元素大於上面元素,就計算上面元素的最長路徑
	if (i > 0 && a[cur] > a[up])
	{
		//如果上面元素的標記為false,說明上面元素的最長路徑還沒有計算
		if (!flag[up])
		{
			//計算上面元素的最長路徑,計算結束後將上面元素的標記賦值為true
			umax = FindMax(a, res, flag, m, n, i-1, j);
			res[up] = umax;
			flag[up] = true;
		}
		//如果上面元素的標記為true,說明已經計算過,直接取結果
		else
			umax = res[up];
	}

	//下面的元素
	int down = (i + 1) * n + j;
	if (i < m-1 && a[cur] > a[down])
	{
		if (!flag[down])
		{
			dmax = FindMax(a, res, flag, m, n, i+1, j);
			res[down] = dmax;
			flag[down] = true;
		}
		else
			dmax = res[down];
	}

	//左邊的元素
	int left = i * n + j - 1;
	if (j > 0 && a[cur] > a[left])
	{
		if (!flag[left])
		{
			lmax = FindMax(a, res, flag, m, n, i, j-1);
			res[left] = lmax;
			flag[left] = true;
		}
		else
			lmax = res[left];
	}

	//右邊的元素
	int right = i * n + j + 1;
	if (j < n-1 && a[cur] > a[right])
	{
		if (!flag[right])
		{
			rmax = FindMax(a, res, flag, m, n, i, j+1);
			res[right] = rmax;
			flag[right] = true;
		}
		else
			rmax = res[right];
	}

	//當上下左右元素的值都計算完,就可以計算當前元素的最長路徑了
	flag[cur] = true;
	res[cur] = 1 + Max(umax, dmax, lmax, rmax);
	return res[cur];
}

int LDS(int a[], int m, int n)
{
	assert(a != NULL && m > 0 && n > 0);
	int* res = new int[m * n];
	memset(res, 0, m*n*sizeof(int));
	bool* flag = new bool[m * n];
	memset(flag, false, m*n*sizeof(bool));
	int max = 0;
	//計算以每個元素起始的最長路徑長度,並記錄全域性的最長路徑長度max
	for (int i = 0; i < m; ++i)
	{
		for (int j = 0; j < n; ++j)
		{
			int cur = i * n + j;
			if (!flag[cur])
				FindMax(a, res, flag, m, n, i, j);
			if (max < res[cur])
				max = res[cur];
		}
	}
	delete [] res;
	delete [] flag;
	return max;
}


void main()
{
	int a[] = {
		100,100,18,19,20,
		10,100,16,100,14,
		12,14,15,100,12,
		100,100,11,100,11,
		100,100,9,100,7};
	int m = 5, n = 5;
	cout << LDS(a, m, n) << endl;
}