基础算法题-数组相关

算法解析:数组问题高效解决方案
这篇博客探讨了数组相关的算法题,包括寻找超过一半次数的数字、计算丑数、求连续正数序列和逆序对总数的方法。还讨论了解决矩阵中字符串路径存在的回溯法策略。文章提供了多种时间复杂度为O(n)的解决方案,并附有代码示例。

1. 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。

如果直接排序,后进行选择,则时间复杂度最优为O(nlogn),这里有两种思路。
第一种,利用快速排序的思想,找到第n/2位置的数。然后扫描数组,确保这个数的确超过数组长度的一半。
第二种,设置两个标记,一个记录值,一个记录次数,当后一个与前一个不同时,记录数减一,当记录数减少到0时候,需要更改值的标记。
时间复杂度都为O(n),两种方法代码分别如下:

int MoreThanHalfNum_Solution1(int[] array) {
    int length = array.length;
    //基于快排思想
    int loc = partition(array, 0, length - 1);
    while (loc != length / 2) {
        if (loc > length / 2) {
            loc = partition(array, 0, loc - 1);
        } else {
            loc = partition(array, loc + 1, length - 1);
        }
    }
    int value = array[loc];
    int count = 0;
    for (int i = 0; i < length; i++) {
        if (array[i] == value)
            count++;
    }
    if (count > length / 2)
        return value;
    else
        return 0;
}
int partition(int[] array, int low, int high) {
    if (low >= high) return low;
    int temp = array[low];
    while (low < high) {
        while (low < high && array[high] >= temp) high--;
        array[low] = array[high];
        while (low < high && array[low] <= temp) low++;
        array[high] = array[low];
    }
    array[low] = temp;
    return low;
}

设置两个标记来处理,最后还得验证是否超过一半:

int MoreThanHalfNum_Solution2(int[] array) {
    int length = array.length;
    int key = array[0], times = 1;
    for (int i = 1; i < length; i++) {
        if (times == 0) {
            key = array[i];
            times++;
        } else {
            if (array[i] != key)
                times--;
            else
                times++;
        }
    }
    int count = 0;
    for (int i = 0; i < length; i++) {
        if (array[i] == key)
            count++;
    }
    if (count > length / 2)
        return key;
    else
        return 0;
}

2.把只包含素因子2、3和5的数称作丑数(Ugly Number), 求按从小到大的顺序的第N个丑数。

不考虑的效率的话,直接迭代去判断每个数是否是丑数,直到得到第N个丑数。效率比较低下。
由于后面的丑数可以通过前面的丑数乘上因子获得,现在设置三个标记来标记依次产生的最小丑数,这个丑数则为下一个丑数,则这个丑数的标记前进一步,若多个标记产生的丑数都是最小的,则多个标记共同前进一步,直到获得第N个丑数。
代码如下:

int GetUglyNumber_Solution(int index) {
    if (index < 2) return index;
    int[] uglyArray = new int[index];
    uglyArray[0] = 1;
    int index_2 = 0, index_3 = 0, index_5 = 0;
    int i = 0, max = 0;
    while (i < index - 1) {
        int max_2 = uglyArray[index_2] * 2;
        int max_3 = uglyArray[index_3] * 3;
        int max_5 = uglyArray[index_5] * 5;
        max = max_2 <= max_3 ? max_2 : max_3;
        max = max <= max_5 ? max : max_5;
        uglyArray[++i] = max;
        if (max == max_2)
            index_2++;
        if (max == max_3)
            index_3++;
        if (max == max_5)
            index_5++;
    }
    return uglyArray[i];
}

3.输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序。

若限制为两个数的和,可以首尾各设一个指针,计算两个指针指向值的和,大于S,则尾指针向前移动,再迭代计算。若小于S,则首指针向后移动,计算和。一直到满足要求为止。
这个题目要求求出连续数字的和,不限元素个数。这里继续设置两个指针,一个放在第一位,一个放在第二位,求累积和,小于S则后面指针向后移动,再累计比较。若大于S则前一个指针向后移动,再累计求和比较,直到满足要求为止,可以求出多个结果。

ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {
    ArrayList<ArrayList<Integer>> array = new ArrayList<ArrayList<Integer>>();
    ArrayList<Integer> one = new ArrayList<Integer>();
    int len = sum / 2 + 1;
    int start = 1, end = 2;
    int total = start + end;
    while (start != len) {
        if (total == sum) {
            for (int i = start; i <= end; i++) {
                one.add(i);
            }
            array.add((ArrayList<Integer>)one.clone());
            one.clear();
            if (end == len)
                break;
            else {
                total += ++end;
            }
        }
        if (total < sum) {
            total += ++end;
        }
        if (total > sum) {
            total -= start++;
        }
    }
    return array;
}

4.在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数

如果直接暴力来计算,可以想象成直接插入排序算法,当数组通过插入排序算法处理完毕时候,数字移动的个数就是逆序对的总数。时间复杂度没有下面的算法优。
利用归并算法。将数组分为两部分,将这两部分再递归处理。当两个数组合并成有序的序列时候,进行逆序统计,生成新排序数组是从数组尾部开始生成,也就是从两个有序的数组中,先找最大的数。设置两个指针,一个指向前一个有序数组的尾部,一个指向后一个有序数组的尾部。进行比较,当前一个大比后一个大的时候,逆序数增加后一个有序数组的头部到指针处的元素个数(若后一指针索引为j,头部的索引为low,则增加j-low+1个),前一个指针前移。当后一个指针大,则逆序数不变。
最后的总逆序数为前一半数组的逆序数加上后一半数组的逆序数,再加上两个数组合并过程中统计的逆序数,即为总逆序数。

int InversePairs(int [] array) {
    if(array==null||array.length==0)
        return 0;
    int length = array.length;
    return merge(array,0,length-1);
}
int merge(int[] array, int low, int high) {
    int count1 = 0, count2 = 0;
    if (low < high) {
        int mid = (low + high) / 2;
        count1 = merge(array, low, mid);
        count2 = merge(array, mid + 1, high);
        count1 = count1 + count2 + mergeAndCount(array, low, mid, high);
    }
    return count1;
}
int mergeAndCount(int[] array, int low, int mid, int high) {
    int[] b = new int[array.length];
    for (int i = 0; i < b.length; i++) {
        b[i] = array[i];
    }
    int count = 0;
    int i, j, k;
    for (i = mid, j = high, k = high; i >= low && j > mid; ) {
        if (b[i] > b[j]) {
            count += j - mid;
            array[k--] = b[i--];
        } else {
            array[k--] = b[j--];
        }
    }
    while (i >= low) array[k--] = b[i--];
    while (j > mid) array[k--] = b[j--];
    return count;
}

>5. 设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。
回溯法,设置一个标记,标记是否可以访问。匹配成功一个字符后,则向四个方向继续匹配,失败则清除不可访问标记。
代码如下:

boolean hasPath(char[] matrix, int rows, int cols, char[] str) {
    int[] flag = new int[matrix.length];
    for (int i = 0; i < rows; i++)
        for (int j = 0; j < cols; j++) {
            if (isHas(matrix, rows, cols, i, j, str, 0, flag))
                return true;
        }
    return false;
}
boolean isHas(char[] a, int rows, int cols, int i, int j, char[] str, int k, int[] flag) {
    int index = i * cols + j;
    if (i < 0 || j < 0 || i >= rows || j >= cols || flag[index] == 1 || a[index] != str[k])
        return false;
    if (k == str.length - 1)
        return true;
    flag[index] = 1;
    if (isHas(a, rows, cols, i - 1, j, str, k + 1, flag) || isHas(a, rows, cols, i + 1, j, str, k + 1, flag) ||
            isHas(a, rows, cols, i, j - 1, str, k + 1, flag) || isHas(a, rows, cols, i, j + 1, str, k + 1, flag))
        return true;
    flag[index] = 0;//失败则去除标记
    return false;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值