算法设计课程练习-分治

一、最大二叉树Pro01

在这里插入图片描述
样例输入

3 2 1 6 0 5

样例输出

6 3 null 2 null 1 5 0 null 

思路分析
1.二叉树的构造:这里显然需要通过递归来构造最大二叉树。
①对于输入的数组,范围为first-end,找到范围数组内的最大值赋予新的节点Node,由此将数组分为了左右两部分;
②对于数组的左半部分,用于递归构造新节点的左子树,范围缩小到first-(max_index-1),其中max_index为最大值的下标;
③同理,对于数组的右半部分,用于递归构造新节点的右子树,范围缩小到(max+1)-end;

2.特殊的前序遍历:由给出的样例可以看出,非叶子节点的左右子树都需要输出,只有叶子节点无需输出null,非叶子节点如果不存在左子树/右子树则需要输出null。

3.其他:①EOF:在输入时ctrl+z回车;②节点的结构体Node根据需要进行修改,但是基本上都含有data,*lchild,*rchild。
AC代码:

#include<bits/stdc++.h>
#define N 1000
using namespace std; 

struct Node {
      int data;
      Node *left;
      Node *right;
      Node() : data(0), left(nullptr), right(nullptr) {}
      Node(int x) : data(x), left(nullptr), right(nullptr) {}
  };
	
Node* creat(const vector<int>& nums, int left, int right) {
    if (left > right) {
        return nullptr;
    }
    //获取范围内的最大值的下标 
    int best = left;
    for (int i = left + 1; i <= right; ++i) {
        if (nums[i] > nums[best]) {
            best = i;
        }
    }
    Node* node = new Node(nums[best]);
    //递归构造左右子树 
    node->left = creat(nums, left, best - 1);
    node->right = creat(nums, best + 1, right);
    return node;
}
	
Node* Build(vector<int>& nums,int k) {//二叉树建立函数 
    return creat(nums, 0, k);
}
   
void PreOrder(Node* bt)//改造版前序遍历函数 
{
	cout<<bt->data<<" ";
	//如果是叶子节点,就无需输出null 
	if(bt->left==nullptr&&bt->right==nullptr) 
		return; 
	else{
		if(bt->left!=nullptr)
			PreOrder(bt->left);
		else
			cout<<"null ";
		if(bt->right!=nullptr)
			PreOrder(bt->right);
		else
			cout<<"null ";
		return;
	}	
} 

int main(){
	vector<int> num(N,0);
	int n;//输入的数 
	int i=0;
	while(cin>>n){
		num[i]=n;
		i++;
	}
	Node* bt = Build(num,i-1);
	PreOrder(bt);
	return 0;
}

二、寻找多数Pro02

在这里插入图片描述
样例输入

7
4 1 2 3 3 3 3

样例输出

3

样例输入

9
4 2 2 2 2 21 23 23 2

样例输出

2

思路分析
①注意这里的条件——多数是出现的次数sum>n/2,因此首先对整个数组进行排序处理;
②排序后所有相同的数字会聚集到一起,假设数组中的a[0]为多数,出现次数初始化为sum=1,对数组进行遍历;
③下一个数和设置的多数num相同则sum++,不同则sum–,直到sum=0时,设置下标i所在的数为新的多数(比如11222,2的次数多于1,所以更新新的多数为2,之所以可以这么做是有sum>n/2这个条件);
④此外,还需要用flag记录一下出现的次数,用于赋值新的多数出现的次数。
AC代码:

#include<bits/stdc++.h>
#define N 10000
using namespace std;

int main()
{
	int n;
	cin>>n;//数组长度
	int a[n];
	for(int i=0 ; i<n ; i++)
		cin>>a[i];
	sort(a,a+n);//排序
	int num = a[0];//设第一个数为多数
	int sum=1;//多数出现的次数 
	int flag[N]={0};//标志数组,算次数 
	flag[a[0]]=1;
	
	for(int i=1 ; i<n ; i++)
	{
		if(sum<1)
		{
			num = a[i];
			sum = flag[a[i-1]]+1;//前一个多数的出现的次数+1 
		}
		else
		{
			if(a[i]==num)
			{
				sum++;
				flag[a[i]]++; 
			}	
			else
				sum--;
		}		
	} 
	
	cout<<num;
	return 0;
 } 

三、找到最大子序和Pro03

在这里插入图片描述
样例输入

9
-2 1 -3 4 -1 2 1 -5 4

样例输出

6

解释:第一个样例解释:连续子数组 [4,-1,2,1]的和最大,为 6。
思路分析
①设置每个节点开始所能得到的子串的和的max为第一个节点;
②从数组的第i个数开始往后构造子串,并计算子串的和sum,如果sum大于max[i],就对max进行更新,否则不变;
③最后对max进行sort排序,输出最大值即可。
AC代码:

#include<bits/stdc++.h>
using namespace std;

int main(){
	int n;
	cin>>n;
	int num[n];
	int max[n];//用于记录从各个节点开始往后的最大值
	for(int i=0 ; i<n ; i++){
		cin>>num[i];
		max[i]=num[i];//设初始节点为最大值 
	}

	for(int i=0 ; i<n-1 ; i++){
		int sum=num[i];
		for(int j=i+1 ; j<n ; j++){
			sum+=num[j];
			if(sum>max[i])//和大于max就更新 
				max[i]=sum;
		}
	} 
	sort(max,max+n);
	cout<<max[n-1];
	return 0;
} 

四、找到 k 个最小数Pro04

在这里插入图片描述
样例输入

3 2
3 2 1

样例输出

1 2

思路分析:sort排序,输出前k个数即可。
AC代码:

#include<bits/stdc++.h>
using namespace std;

int main(){
	int n,k;
	cin>>n>>k;
	int arr[n];
	for(int i=0 ; i<n ; i++)
		cin>>arr[i];
	sort(arr,arr+n);
	for(int j=0 ; j<k ; j++)
		cout<<arr[j]<<" ";
	return 0;
}

五、寻找第 k 个最大元素Pro05

在这里插入图片描述
样例输入

6 2
3 2 1 5 6 4

样例输出

5

思路分析:sort排序,输出第n-k个数即可。
AC代码:

#include<bits/stdc++.h>
using namespace std;

int main(){
	int n,k;
	cin>>n>>k;
	int num[n];
	for(int i=0 ; i<n ; i++)
		cin>>num[i];
	sort(num,num+n);
	cout<<num[n-k];
	return 0;
}

六、找到 k 个最长重复字符串Pro06

在这里插入图片描述
样例输入

aaabb 3

样例输出

3

样例输入

ababbc 2

样例输出

5

样例输入

abcde 2

样例输出

0

思路分析:这题是很明显的递归分治思想,分左右子串递归找到满足条件的最大子串。
①构建findMax函数,用于返回满足条件的最大子串的长度;
②首先用cnt[26]数组来统计输出的子串中各个字母出现的次数;
③设置flag来标明输入的子串是否是最大子串,flag初始化为flase,即假设输入的子串就满足子串中的字母出现次数都不小于k的条件,遍历输入的子串,如果都满足条件,最终返回输入的子串的长度len;
④遍历输入的子串,如果找到有不满足条件的字母,flag=true,从不满足的字母开始将子串分为左右两个子串,递归计算左右两个子串满足条件的最大子串的长度,并取最大值,并退出循环,返回这个最大值。
AC代码:

#include<bits/stdc++.h>
using namespace std;

int findMax(string s,int k){
	int len = s.size();
	if(len<k) return 0;
	
	int cnt[26]={0};//用于存储各个字母出现的次数
	for(int i=0 ; i<len ; i++)
		cnt[s[i]-'a']++;
		
	int flag = false;
	int m;
	for(int i=0 ; i<len ; i++){
		if(cnt[s[i]-'a']<k){
			flag = true;
			m = max(findMax(s.substr(0,i),k),findMax(s.substr(i+1,len-i-1),k));//在不满足大于k的字符出断开,递归查找两个子串中满足条件的最大子串 
			break;
		} 
	}
	if(!flag)//如果s满足条件,则最大子串的长度就是s的长度 
		return len;
	else//如果不满足条件,则最大字串长度就是左右子串中的最大字串长度的大的那一个 
		return m;
}

int main(){
	int k;//子串中各个字母出现的次数 
	string s;
	cin>>s>>k;
	int m = findMax(s,k);
	cout<<m;
	return 0; 
}

七、链表排序Pro07

在这里插入图片描述

struct ListNode {
    int val;
    ListNode *next;
    ListNode() : val(0), next(nullptr) {}
    ListNode(int x) : val(x), next(nullptr) {}
    ListNode(int x, ListNode *next) : val(x), next(next) {}
};

在这里插入图片描述
样例输入

4 2 1 3

样例输出

1 2 3 4

样例输入

-1 5 3 4 0

样例输出

-1 0 3 4 5

思路分析:本题采用自顶向下的归并排序方法。
①利用快慢指针找到输入的链表的中点,然后将输入的链表断开成左右链表,然后递归使用sortList对左右链表进行排序,当只有一个节点的时候结束递归;
②然后对排序后的左右链表进行合并排序操作mergeList,返回完整的一个有序列表。
AC代码:

#include<bits/stdc++.h>
using namespace std;

struct ListNode {
    int val;
    ListNode *next;
    ListNode() : val(0), next(nullptr) {}
    ListNode(int x) : val(x), next(nullptr) {}
    ListNode(int x, ListNode *next) : val(x), next(next) {}
};

//合并函数,将两个链表合并为一个链表 
ListNode *mergeList(ListNode *list1, ListNode *list2)
{
    ListNode *dummyHead = new ListNode(0);
    ListNode *merge_temp = dummyHead;
    ListNode *list1_temp = list1;
    ListNode *list2_temp = list2;
    while (list1_temp && list2_temp)
    {
        if (list1_temp->val <= list2_temp->val)
        {
            merge_temp->next = list1_temp;
            list1_temp = list1_temp->next;
        }
        else
        {
            merge_temp->next = list2_temp;
            list2_temp = list2_temp->next;
        }
        merge_temp = merge_temp->next;
    }
	//可能存在其中一个链表提前融合完的情况,因此要进行扫尾  
    if (list1_temp)
        merge_temp->next = list1_temp;

    if (list2_temp)
        merge_temp->next = list2_temp;

    return dummyHead->next;
}

ListNode *sortList(ListNode *head, ListNode *tail)
{
    // 递归退出条件,即划分至只有一个结点
    if (head->next == tail)
    {
        head->next = nullptr; // 断链
        return head;
    }
	//利用快慢指针找到中间节点(快指针的步数是慢指针的两倍,因此快指针到结尾的时候,慢指针刚好到中间)
    // 使用快慢指针找出链表的 mid 结点
    // slow 最终得到的是真实 mid 的后一个结点
    // fast 最终得到的是最后一个结点的后一个结点
    ListNode *fast = head;
    ListNode *slow = head;
    while (fast != tail)
    {
        fast = fast->next;
        slow = slow->next;
        if (fast != tail)
            fast = fast->next;
    }

    ListNode *mid = slow;    // mid 是第二个链表的头结点
    return mergeList(sortList(head, mid), sortList(mid, tail));
}

int main(){
	int k;
	ListNode *head = new ListNode(0,nullptr);
	ListNode *p = head;
	while(cin>>k) {//构造链表 
		ListNode *bt = new ListNode(k);
		p->next = bt;
		p = bt;
	}
	p->next = nullptr;//最后维护尾节点 
	ListNode *n = sortList(head->next,p->next);
	while(n!=nullptr){
		cout<<n->val<<" ";
		n=n->next;
	}
	return 0;
}

六、找到 k 个最长重复字符串Pro06

在这里插入图片描述
样例输入

3
2 2 1

样例输出

3

样例输入

5
10 2 10 10 10

样例输出

2

样例输入

7
150 150 150 10 150 150 150

样例输出

4

思路分析:这题是很明显的递归分治思想,分左右子串递归找到假币。
①首先得到正常的硬币的重量,因为假币的重量小于正常的硬币所以直接对硬币数组a取max得到正常的重量;
②分左右子序列找到假币,如果mid对应的硬币小于正常的重量,则该币就是假币;
③对左右子序列中总重量小于正常的总重量的子序列进行递归查找。
AC代码:

#include<bits/stdc++.h>
using namespace std;
int weight;//存储正常的重量
int n;//存储硬币的数量
 
int sum(vector<int> a,int first,int end){
	int s=0;
	for(int i=first ; i<=end ; i++)
		s += a[i];
	return s;
}

void find(vector<int> a,int first,int end){
	int mid = (first+end)/2;
	if(a[mid]<weight)//mid下表对应的币值不对直接打印 
		cout<<mid;
	else{//否则继续检查左右两个子序列 
		if(sum(a,first,mid-1)<weight*(mid-first))
			find(a,first,mid-1);
		else if(sum(a,mid+1,end)<weight*(end-mid))
			find(a,mid+1,end);
	}
}

int main(){
	cin>>n;
	vector<int> a(n+1,0);
	int i=1;
	int tmp;
	while(scanf("%d",&tmp)!=EOF)
		a[i++] = tmp;
	weight = *max_element(a.begin(),a.end());//数组中的最大值就是正常的重量 
	find(a,1,n);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值