【算法和数据结构】模拟和暴力

模拟

有些问题的解题步骤,就是我们思考的步骤。这时可以模拟我们的思考,一步步解决问题。

约瑟夫环

问题:N个人围成一圈,从 1 开始报数,第M个出列,然后下一个人继续从 1 开始报数,直到最后剩下一个。

题目已经很清楚的给出了解题步骤,我们只需要构造一个循环链表(或者用数组,但需要注意越界时跳到数组的第一个元素来实现循环效果)。

数组

#include <stdio.h>

void mov(int arr[], int n, int cur) {
    for (int i = cur; i< n - 1; i++) {
        arr[i] = arr[i + 1];
    }
}

void joseph(int n, int m, int ret[]) {
    int arr[n];
    for (int i = 0; i < n; i++) {
        arr[i] = i + 1;
    }
    int left = n;
    int cur = 0;
    while (left > 0) {
        // for (int i = 0; i < m - 1; i++) {
        //     cur++;
        //     if (cur >= left) {
        //         cur = 0;
        //     }
        // }
        cur = (cur + m - 1) % left;
        ret[n - left] = arr[cur];
        mov(arr, left, cur);
        left--;
        if (cur >= left) {
            cur = 0;
        }
    }
}

int main(void) {
    int n = 7;
    int m = 3;
    int ret[n];
    joseph(n, m, ret);
    for (int i = 0; i < n; i++) {
        printf("%d\t", ret[i]);
    }

    return 0;
}

链表

首先构造循环链表,然后开始数数,每次从1数到m后,剔除这个元素,开始下次数数。

注意,C 语言的结构体变量声明后会在栈空间分配一个固定内存空间。如果需要多个结构体,需要用 malloc 去堆空间申请内存。

#include <stdio.h>
#include <malloc.h>

typedef struct Node {
    int value;
    struct Node *next;
} Node;

typedef struct List {
    Node *head;
    Node *tail;
} List ;

void joseph(int arr[], int n, int m, int ret[]) {
    List list = {NULL, NULL};
    int count = 0;

	// build cycle linked list
    Node *tmp = (Node *)malloc(sizeof(Node));
    tmp->value = arr[n - 1];
    tmp->next = tmp;
    list.head = tmp;
    list.tail = tmp;
    for (int i = n - 2; i >= 0; i--) {
        Node *tmp = (Node *)malloc(sizeof(Node));
        tmp->value = arr[i];
        tmp->next = list.head;
        list.head = tmp;
        list.tail->next = tmp;
    }
    
    Node *cur = list.tail;
    Node *fr;
    while (cur->next != cur) {
        for (int i = 0; i < m - 1; i++) {
            cur = cur->next;
        }
        ret[count] = cur->next->value;
        count++;
        fr = cur->next;
        cur->next = cur->next->next;
        free(fr);
    }
    ret[count] = cur->value;
    free(cur);
}

int main(void) {
    int arr[] = {1,2,3,4,5,6,7};
    int n = 7;
    int m = 3; // from 1
    int ret[n];
    joseph(arr, n, m, ret);
    for (int i = 0; i < n; i++) {
        printf("%d\t", ret[i]);
    }

    return 0;
}

暴力

枚举所有的可能,然后判断哪些符合条件。

算法简单,需要考虑时间复杂度。

求满足条件的最小值

Sn = 1 + 1/2 + 1/3 + … + 1/n。给出一个整数K(1 <= K <= 15),计算最小的 n,使得 Sn > K。

#include <stdio.h>

int min(int k) {
    float sum = 0;
    for (int i = 1; ; i++) {
        sum += 1.0 / i;
        if (sum > (float)k) {
            return i;
        }
    }
}

int main(void) {
    printf("%d\n", min(1));
    printf("%d\n", min(3));
    printf("%d\n", min(15));

    return 0;
}

百钱百鸡

用一百元买一百只鸡,其中公鸡3元一只,母鸡5元一只,小鸡一元3只。列出所有方案。

对于这个问题,实际上就是求三元一次方程的所有非负整数解。因为只有两个约束条件,所以可能有多个解。条件为:

  • x + y + z = 100;
  • 3 * x + 5 * y + z / 3 = 100;
  • x, y, z 都是非负整数

完全暴力

直接遍历所有可能,复杂度为 O(n^3):

#include <stdio.h>

void j() {
    for (int i = 0; i < 100; i++) {
        for (int j = 0; j < 100; j++) {
            for (int k = 0; k < 100; k++) {
                if (i + j + k == 100 && 
                    k % 3 == 0 &&
                    3 * i + 5 * j + k / 3 == 100) {
                        printf("%d, %d, %d\n", i, j, k);
                    }
            }
        }
    }
}

int main(void) {
    j();

    return 0;
}

输出:

4, 12, 84
11, 8, 81
18, 4, 78
25, 0, 75

利用已知条件

对于三元一次方程,如果只有一个表达式,则可以把其中的一个变量用另外两个变量来表示,从而只遍历2次。如果有两个表达式,可以把其中的两个变量用另外一个变量表示,从而只遍历1次。

y = (200 - 8 * x) / 14
z = (400 - 2 * x) / 14 * 3
#include <stdio.h>

void j() {
    for (int i = 0; i < 100; i++) {
        int j = (200 - 8 * i) / 14;
        int k = (400 - 2 * i) / 14 * 3;
        if (i + j + k == 100 && 
            i >= 0 && j >= 0 && k >= 0 && 
            k % 3 == 0 &&
            3 * i + 5 * j + k / 3 == 100) {
                printf("%d, %d, %d\n", i, j, k);
            }
    }
}

int main(void) {
    j();
    return 0;
}

求一个整数能不能被0~9组成的两个五位数相除

任意正整数n(2 <= n <= 70),写出所有形式如 abcde/fghij 的除法,其中 a 到 j 刚好是数字 0 到 9,且商等于 n。

这里可以通过递归构造所有满足要求的数字,然后逐个判断是否符合要求。

暴力

暴力枚举每个位置的数字,时间复杂度是 O(10!)。时间复杂度太高,省略代码,可以看下面的暴力加反向乘法。

暴力加反向乘法

对除数进行暴力,然后乘以 n,再判断结果是否满足要求,时间复杂度 O(5!)

package main

import (
    "fmt"
    "strconv"
)

func main() {
    mul(3)
}

func mul(a int) {
    var all [][2]int
    var ret []int
    var noused []int
    for j := 0; j < 10; j++ {
        noused = append(noused, j)
    }
    build([]int{}, noused, &ret)
    for _, v := range ret {
        tmp := a * v
        if match(tmp, v) {
            all = append(all, [2]int{tmp, v})
        }
    }
    fmt.Println(all)
}

// 判断两个数字是否恰好由0~9这10个数字组成
func match(a int, b int) bool {
    var all = make(map[int]bool, 0)
    s := strconv.Itoa(a) + strconv.Itoa(b)
    for i := 0; i < len(s); i++ {
        v := s[i:i+1]
        vint, _ := strconv.Atoi(v)
        if _, ok := all[vint]; ok {
            return false
        }
        all[vint] = true
    }
    if _, ok := all[0]; len(s) == 9 && !ok {
        all[0] = true
    }
    if len(all) == 10 {
        return true
    }
    return false
}

// 递归构造所有满足要求的数字
func build(prefix []int, noused []int, ret *[]int) {
    if len(prefix) == 5 {
        num := 0
        for _, v := range prefix {
            num = num * 10 + v
        }
        *ret = append(*ret, num)
        return
    }
    for i, v := range noused {
        var noused_new = make([]int, len(noused))
        var prefix_new = make([]int, len(prefix))
        copy(noused_new, noused)
        copy(prefix_new, prefix)
        noused_new = append(noused_new[:i], noused_new[i + 1:]...)
        prefix_new = append(prefix_new, v)
        build(prefix_new, noused_new, ret)
    }
}

结果:

[[17469 5823] [17496 5832] [50382 16794] [53082 17694] [61749 20583] [69174 23058] [91746 30582] [96174 32058]]

四则运算

给定一个全是数字的字符串(长5至20),按顺序加入+-*/ 四个运算符,求最大的计算结果。有 q 组询问(1 <= q <= 100000)。

例如,对于 12345,其最大结果也是唯一结果,即 1+2-3*4/5 = 1

如果直接暴力,每次大概需要计算 20^4 种情况,乘以 q 组后会超时。分析表达式可知,对于 a+b-c*d/e,只需要 a b e 尽可能大,c 和 d 尽可能小,结果就是符合要求的。

判断给出的整数数组中能不能取3个组成三角形

三角形三条边的规则:两条短边长度之和一定大于最长边。

给定一个整数数组,最大不超过 1e9,判断其中是否存在可以构成三角形的3个整数。

直接暴力,时间复杂度为 O(n^3)。

如果先对所有元素排序,然后取任意相邻的三个元素,可以发现,如果这3个元素中的两个较小值A B的和都小于较大者C,那么更小的所有元素求和时也会小于较大者C。所以,无法组成三角形的边界条件是:**排序后,对任意相邻3个元素ABC,A+B=C,此时刚好无法构成三角形。**这个条件对应的就是斐波那契数列。对于 1e9,对应斐波那契数列的第 45 个元素,所以一旦给定的数组中的元素大于 45,可以认为一定会构成三角形,否则使用暴力或者排序后判断所有的相邻3个元素是否构成三角形。

看看 1e9 对应斐波那契数列的第几个元素

#include <stdio.h>

int fib(int target) {
    int count = 0;
    int cur = 0;
    int next = 1;
    while (cur < target) {
        next = next + cur;
        cur = next - cur;
        count++;
    }
    return count;
}

int main(void) {
    printf("%d\n", fib(1e9));
    return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值