#数据结构与算法学习笔记#剑指Offer27:数组中出现次数超过一半的数字 + 3种解法 + 测试用例(Java、C/C++)

版权声明:本文为博主NJU_ChopinXBP原创文章,发表于CSDN,仅供交流学习使用,转载请私信或评论联系,未经博主允许不得转载。感谢您的评论与点赞。 https://blog.csdn.net/qq_20304723/article/details/82390126

2018.9.4

这道题让我想起一道很早之前做过的题目——求最大子列和。最佳思路惊人地相似,以后碰上对一条数组进行操作的题目可以多往分治算法和在线处理算法上多思考。

#数据结构与算法学习笔记#PTA3:在线处理算法求最大子列和,并返回最大子列和头尾元素(C/C++)(2004年浙大计科考研复试)

#数据结构与算法学习笔记#PTA4:分治算法求最大子列和(C/C++)

这道题实现难度不大,关键点在于如何改进算法提高时间复杂度。主要思路有三个:

方法一:遍历计数,o(n^{2})。遍历数组的同时对出现过的数字与次数进行记录,由于对一些高级的数据结构不够熟悉,用了个Data类对两者进行存储,但是查找的时候仍然是遍历查找,因此复杂度还是o(n^{2}

改进版方法一:HashMap,o(n)。没错就是HashMap,HashMap的key值可以存储数字,val值存储次数,不仅省去了Data类的实现,而且查找效率o(1)。整体时间复杂度降为o(n),典型的空间换时间,大大提高了效率。完美地诠释了学好数据结构的重要性。

方法二:快速寻找中位数,o(n)。首先要想到——如果数组中存在一个数字出现次数超过一半,那么按照顺序排序之后,数组的中位数必然会是该数。那么我们只需要找到数组的中位数就可以了。可是一般的排序算法时间复杂度是o(nlogn),因此需要借助快排的思想。

每次递归以flag为界,不断交换左右元素,左边元素全小于flag所对应的值,右边元素全大于flag所对应的值,返回flag所在位置。不断调整与递归,若index正好等于中位middle,则该数就是中位数。最后只要确认这个中位数出现的次数即可。

方法三:在线处理算法,o(n)。首先要想到——如果数组中存在一个数字出现次数超过一半,那它比其他所有数字出现次数之和还要多。如果以这个数字与一个不同的数字配对从数组里两两删除的话,最后剩下的数字如果只有一个,那就是这个数字;如果剩下两个以上且相同,那也只能是这个数字。在线处理算法也一定程度上沿袭了这种思想,可以想象成一个士兵不断推进的过程。

将起始次数times设置为1,起始数字result设置为第一个数。不断向右推进,若当前次数为0,将result更新为当前数字。若当前数字与result相同,次数+1;若不同则-1。接下来只要确认result出现的次数即可。


题目描述

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。由于目标数字出现次数比其他数字加起来还多,因此最后result中存的数字为怀疑的目标数字。


Java实现(三种方法):

/**
 * 
 * @author ChopinXBP
 * 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
 * 例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
 * 
 */

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;


public class MoreThanHalfNum_Solution_27 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int [] array = {1,2,3,2,2,2,5,4,2};
		System.out.println(MoreThanHalfNum_Solution2(array));
	}

	
	//////////////////////方法一:遍历计数///////////////////////
	public static class Data{
		int data;
		int num;
		Data(int data){
			this.data = data;
			num = 1;
		}
	}
	
	public static int MoreThanHalfNum_Solution(int [] array) {
		if (array.length <= 0) return 0;
		
        HashSet<Data> datalist = new HashSet<>();
        for(int i = 0; i < array.length; i++){
        	if(!isExistNum(datalist, array[i])){
        		Data newdata = new Data(array[i]);
        		datalist.add(newdata);
        	}
        }
        
        int result = 0;
        for(Data it : datalist){
			if(it.num > array.length / 2){
				result = it.data;
			}
		}
        
        return result;
    }
	
	public static boolean isExistNum(HashSet<Data> datalist, int i){
		for(Data it : datalist){
			if(it.data == i){
				it.num++;
				return true;
			}
		}
		return false;
	}
	
	//////////////////////方法一改进:HashMap///////////////////////
	public static int MoreThanHalfNum_Solution1(int[] array) {
		//利用HashMap,key值存储元素,val值存储次数
		HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();

		for (int i = 0; i < array.length; i++) {

			if (!map.containsKey(array[i])) {
				map.put(array[i], 1);
			} else {
				int count = map.get(array[i]);
				map.put(array[i], ++count);
			}
		}
		
		Iterator iter = map.entrySet().iterator();
		while (iter.hasNext()) {
			Map.Entry entry = (Map.Entry) iter.next();
			Integer key = (Integer) entry.getKey();
			Integer val = (Integer) entry.getValue();
			if (val > array.length / 2) {
				return key;
			}
		}
		return 0;
	}
	
	////////////////方法二:快速寻找中位数(快排思想)//////////////////
	public static int MoreThanHalfNum_Solution2(int[] array) {
		if (array.length <= 0)
			return 0;

		int start = 0;
		int length = array.length;
		int end = length - 1;
		int middle = length >> 1;

		int index = Partition(array, start, end);	

		//若index正好等于middle,也就是中位数,若index>middle,则中位数在index左边,若index<middle,则中位数在index右边
		//递归找到将index匹配至中位数位置
		while (index != middle) {
			if (index > middle) {
				index = Partition(array, start, index - 1);
			} else {
				index = Partition(array, index + 1, end);
			}
		}
		int result = array[middle];

		//计算出现次数是否超过一半
		int times = 0;
		for (int i = 0; i < length; ++i) {
			if (array[i] == result)
				times++;
		}
		if (times * 2 < length) {
			System.out.println(times);
			return 0;
		} else {
			return result;
		}
	}

        //先在数组中随机选择一个位置,比选择位置的数字小的数字移到数组的左边,比选择位置的数字大的数字移到数组的右边,该数字回到最终位置,返回该位置
	public static int Partition(int[] array, int start, int end) {
		//找一个start到end之间的随机目标位置,(数据类型)(最小值+Math.random()*(最大值-最小值+1))
		int index = (int)(start + Math.random() * (end - start + 1));		
		
		//将目标位置的值放到最后用于比较,初始化flag值在start,small值为最终所有比目标值小的数之和
		swap(array, index, end);
		int flag = start;
		
		//index从start开始循环至end-1,若出现比目标值(即比end位置的值)小的数,将flag与index互换,flag同时推进一格
		for(index = start; index < end; index++){	
			if(array[index] < array[end]){
				if(flag != index)
					swap(array, index, flag);
				flag++;
			}
		}
		//最后将末尾的数与small互换
		swap(array, flag, end);
		
		return flag;
	}

	public static void swap(int[] array, int num1, int num2) {
		int temp = array[num1];
		array[num1] = array[num2];
		array[num2] = temp;
	}
	
	////////////////方法三:在线处理算法(数组特点)//////////////////
	public int MoreThanHalfNum_Solution3(int[] array) {
		if (array.length <= 0) {
			return 0;
		}
		int result = array[0];
		int times = 1;			//起始次数设置为1	

		//若当前次数为0,将result更新为当前数字。若当前数字与result相同,次数+1;若不同则-1。
		//由于目标数字出现次数比其他数字加起来还多,因此最后result中存的数字为怀疑的目标数字
		for (int i = 0; i < array.length; i++) {
			if (times == 0) {
				result = array[i];
				times = 1;
			}
			else if (array[i] == result)
				times++;
			else
				times--;
		}
		
		//计算result中怀疑的目标数字出现次数,进行最终确定
		int time = 0;
		for (int i = 0; i < array.length; ++i) {
			if (array[i] == result)
				time++;
		}
		
		if (time * 2 < array.length) {
			//System.out.println(time);
			return 0;
		} else {
			return result;
		}
	}

}

C++实现示例(方法二):

bool g_bInputInvalid = false;

bool CheckInvalidArray(int* numbers, int length)
{
    g_bInputInvalid = false;
    if(numbers == NULL && length <= 0)
        g_bInputInvalid = true;

    return g_bInputInvalid;
}

bool CheckMoreThanHalf(int* numbers, int length, int number)
{
    int times = 0;
    for(int i = 0; i < length; ++i)
    {
        if(numbers[i] == number)
            times++;
    }
 
    bool isMoreThanHalf = true;
    if(times * 2 <= length)
    {
        g_bInputInvalid = true;
        isMoreThanHalf = false;
    }

    return isMoreThanHalf;
}


int MoreThanHalfNum_Solution1(int* numbers, int length)
{
    if(CheckInvalidArray(numbers, length))
        return 0;
 
    int middle = length >> 1;
    int start = 0;
    int end = length - 1;
    int index = Partition(numbers, length, start, end);
    while(index != middle)
    {
        if(index > middle)
        {
            end = index - 1;
            index = Partition(numbers, length, start, end);
        }
        else
        {
            start = index + 1;
            index = Partition(numbers, length, start, end);
        }
    }
 
    int result = numbers[middle];
    if(!CheckMoreThanHalf(numbers, length, result))
        result = 0;

    return result;
}

C++实现示例(方法三):

bool g_bInputInvalid = false;

bool CheckInvalidArray(int* numbers, int length)
{
    g_bInputInvalid = false;
    if(numbers == NULL && length <= 0)
        g_bInputInvalid = true;

    return g_bInputInvalid;
}

bool CheckMoreThanHalf(int* numbers, int length, int number)
{
    int times = 0;
    for(int i = 0; i < length; ++i)
    {
        if(numbers[i] == number)
            times++;
    }
 
    bool isMoreThanHalf = true;
    if(times * 2 <= length)
    {
        g_bInputInvalid = true;
        isMoreThanHalf = false;
    }

    return isMoreThanHalf;
}


int MoreThanHalfNum_Solution2(int* numbers, int length)
{
    if(CheckInvalidArray(numbers, length))
        return 0;
 
    int result = numbers[0];
    int times = 1;
    for(int i = 1; i < length; ++i)
    {
        if(times == 0)
        {
            result = numbers[i];
            times = 1;
        }
        else if(numbers[i] == result)
            times++;
        else
            times--;
    }
 
    if(!CheckMoreThanHalf(numbers, length, result))
        result = 0;
 
    return result;
}

测试用例:

// ====================测试代码====================
void Test(char* testName, int* numbers, int length, int expectedValue, bool expectedFlag)
{
    if(testName != NULL)
        printf("%s begins: \n", testName);

    int* copy = new int[length];
    for(int i = 0; i < length; ++i)
        copy[i] = numbers[i];

    printf("Test for solution1: ");
    int result = MoreThanHalfNum_Solution1(numbers, length);
    if(result == expectedValue && g_bInputInvalid == expectedFlag)
        printf("Passed.\n");
    else
        printf("Failed.\n");

    printf("Test for solution2: ");
    result = MoreThanHalfNum_Solution2(copy, length);
    if(result == expectedValue && g_bInputInvalid == expectedFlag)
        printf("Passed.\n");
    else
        printf("Failed.\n");

    delete[] copy;
}

// 存在出现次数超过数组长度一半的数字
void Test1()
{
    int numbers[] = {1, 2, 3, 2, 2, 2, 5, 4, 2};
    Test("Test1", numbers, sizeof(numbers) / sizeof(int), 2, false);
}

// 不存在出现次数超过数组长度一半的数字
void Test2()
{
    int numbers[] = {1, 2, 3, 2, 4, 2, 5, 2, 3};
    Test("Test2", numbers, sizeof(numbers) / sizeof(int), 0, true);
}

// 出现次数超过数组长度一半的数字都出现在数组的前半部分
void Test3()
{
    int numbers[] = {2, 2, 2, 2, 2, 1, 3, 4, 5};
    Test("Test3", numbers, sizeof(numbers) / sizeof(int), 2, false);
}

// 出现次数超过数组长度一半的数字都出现在数组的后半部分
void Test4()
{
    int numbers[] = {1, 3, 4, 5, 2, 2, 2, 2, 2};
    Test("Test4", numbers, sizeof(numbers) / sizeof(int), 2, false);
}

// 输入空指针
void Test5()
{
   int numbers[] = {1};
   Test("Test5", numbers, 1, 1, false);
}

// 输入空指针
void Test6()
{
    Test("Test6", NULL, 0, 0, true);
}

int _tmain(int argc, _TCHAR* argv[])
{
    Test1();
    Test2();
    Test3();
    Test4();
    Test5();
    Test6();

    return 0;
}

#Coding一小时,Copying一秒钟。留个言点个赞呗,谢谢你#

猜你喜欢

转载自blog.csdn.net/qq_20304723/article/details/82390126