How can you determine how to place coins on a watch?

Say you are given a set of coins, such as 4 10 ¢, 4 5 ¢ and 4 1 ¢.

You will be asked to place these coins on a 12-hour analog dial, where the next coin you bet should be placed at X hours after the previous coin, where X is the value of the previous coin.

So, if you put 1 ¢ on 12, the next coin you bet will be 1. If you put 5 ¢ on 1, the next coin you place will be 6. And so on.

How can you maximize the number of coins that can be placed on a watch before the next coin has to be placed in an already selected slot?

This is a problem that I encountered, which I could not solve, except through an exhaustive search. If the input data is made arbitrary, then an exhaustive search quickly ends - say, this is an arbitrary number of coins of any various known denominations with an arbitrary number of hours on the clock. Then you can no longer do an exhaustive search because it becomes factorial time and fails based on excessive computational time requirements.

+4
source share
4 answers

maraca, , , . , "". [5, 10, 10, 5, 10, 10, 5, x] 8 , , . , , .

. 12! / (4! * 4! * 4!) = 34650. . python, 3 , 3*10^15.

max_positions = []
max_order = None

def add_coin(coins, position, coin_order, occupied_positions, num_hours):
    global max_positions, max_order
    if position in occupied_positions or not coins:
        # Can't place on that position or there is nothing more to place
        if len(occupied_positions) > len(max_positions):
            max_positions = occupied_positions
            max_order = coin_order
        return not coins  # if all is covered return true to stop search
    #
    for c, num_coins in coins:  # Try each coin
        # Copy coins to new list and remove one used
        c_coins = [x for x in coins if x[0] != c]
        if num_coins > 1:
            c_coins.append((c, num_coins-1))
        # Next iteration
        if add_coin(c_coins,
                 (position + c) % num_hours,
                 coin_order + [c],
                 occupied_positions + [position],
                 num_hours):
            return True

def solve_coins(coins, num_hours):
    global max_positions, max_order
    max_positions = []
    max_order = None
    add_coin(coins, 0, [], [], num_hours)
    print len(max_positions), max_positions, max_order

solve_coins([(1, 4), (5, 4), (10, 4)], 12)
solve_coins([(1, 8), (5, 8), (10, 8)], 24)
solve_coins([(1, 12), (5, 12), (10, 12)], 36)

:

12 [0, 1, 6, 4, 2, 3, 8, 9, 7, 5, 10, 11] [1, 5, 10, 10, 1, 5, 1, 10, 10, 5, 1, 5]
24 [0, 1, 6, 16, 17, 3, 4, 14, 19, 5, 15, 20, 21, 2, 7, 8, 13, 18, 23, 9, 10, 11, 12, 22] [1, 5, 10, 1, 10, 1, 10, 5, 10, 10, 5, 1, 5, 5, 1, 5, 5, 5, 10, 1, 1, 1, 10, 10]
36 [0, 1, 6, 16, 17, 22, 23, 28, 2, 12, 13, 18, 19, 29, 34, 3, 8, 9, 10, 11, 21, 31, 5, 15, 20, 30, 35, 4, 14, 24, 25, 26, 27, 32, 33, 7] [1, 5, 10, 1, 5, 1, 5, 10, 10, 1, 5, 1, 10, 5, 5, 5, 1, 1, 1, 10, 10, 10, 10, 5, 10, 5, 5, 10, 10, 1, 1, 1, 5, 1, 10, 5]
+3
// Expressing the coins as a list of buckets with the same modulo allows
// you to efficiently find the next coin to test and you don't start to
// calculate with the first penny and then do the same again starting
// with the second penny (or a 13-coin on a 12-clock), it is basically the same.
// Additionally it allows to remove and insert items at the current position in O(1).
// Also reverting is much better than copying whole states on each recursive call.
private class Bucket {
    public int number;
    public LinkedList<Integer> numbers = new LinkedList<>();
    public Bucket(int number, int hours) {
        this.number = number % hours;
        numbers.add(number);
    }
}

private LinkedList<Bucket> coins; // not using interface List as you are supposed to
private LinkedList<Integer> best, current; // because of removeLast()
private boolean[] occupied;
private int hours, limit;

public List<Integer> findBest(int[] coins, int hours) {
    this.hours = hours;
    // create buckets of coins with the same modulo
    Integer[] c = Arrays.stream(coins).boxed().toArray( Integer[]::new );
    // sort descending because a lot of small coins in a row are more likely to create
    // an impassable area on the next pass around the clock
    Arrays.sort(c, new Comparator<Integer>(){
        public int compare(Integer a, Integer b) {
            return Integer.compare(b.intValue() % hours, a.intValue() % hours);
        }
    });
    this.coins = new LinkedList<>();
    Bucket b = new Bucket(c[0].intValue(), hours);
    this.coins.add(b);
    int mod = c[0].intValue() % hours, coinCount = 1;
    for (int i = 1; i < c.length; i++) {
        int m = c[i].intValue() % hours;
        if (m == mod) { // same bucket
            b.numbers.add(c[i]);
        } else { // new bucket
            b = new Bucket(c[i].intValue(), hours);
            this.coins.add(b);
            mod = m;
        }
        coinCount++;
        if (mod == 0) // don't need more than one 0 value
            break;
    }
    best = new LinkedList<>();
    current = new LinkedList<>();
    occupied = new boolean[hours];
    limit = coinCount < hours ? coinCount : hours; // max coins that can be placed
    findBest(0);
    return best;
}

private void findBest(int pos) {
    if (best.size() == limit) // already found optimal solution
        return;
    if (occupied[pos] || current.size() == limit) {
        if (current.size() > best.size())
            best = (LinkedList<Integer>)current.clone();
        return;
    }
    occupied[pos] = true;
    for (int i = 0; i < coins.size(); i++) {
        Bucket b = coins.get(i);
        current.add(b.numbers.removeLast());
        boolean removed = false;
        if (b.numbers.size() == 0) { // bucket empty
            coins.remove(i);
            removed = true;
        }
        findBest((pos + b.number) % hours);
        if (removed)
            coins.add(i, b);
        b.numbers.add(current.removeLast());
    }
    occupied[pos] = false;
}

: 10 10 5 1 1 1 5 10 10 1 5 5


- JavaScript, , , - O (1). , . , , , . , Java.

var head, occupied, current, best, h, limit;

document.body.innerHTML = solve([1,1,1,1,5,5,5,5,10,10,10,10], 12);

function solve(coins, hours) {
    h = hours;
    coins.sort(function(a, b) {
        let x = a % hours, y = b % hours;
        if (x > y)
            return -1;
        if (x < y)
            return 1;
        return 0;
    });
    let mod = coins[0] % hours;
    head = {num: mod, vals: [coins[0]], next: null};
    let b = head, coinCount = 1;
    for (let i = 1; i < coins.length && mod != 0; i++) {
        let m = coins[i] % hours;
        if (m == mod) {
            b.vals.push(coins[i]);
        } else {
            b.next = {num: m, vals: [coins[i]], next: null};
            b = b.next;
            mod = m;
        }
        coinCount++;
    }
    limit = coinCount < hours ? coinCount : hours;
    occupied = [];
    for (let i = 0; i < hours; i++)
        occupied.push(false);
    best = [];
    current = [];
    solveRec(0);
    return JSON.stringify(best);
}

function solveRec(pos) {
    occupied[pos] = true;
    let b = head, prev = null;
    while (b !== null) {
        let m = (pos + b.num) % h;
        if (!occupied[m]) {
            current.push(b.vals.pop());
            let rem = false;
            if (b.vals.length == 0) {
                if (prev == null)
                    head = b.next;
                else
                    prev.next = b.next;
                rem = true;
            }
            solveRec(m);
            if (current.length > best.length)
                best = current.slice();
            if (best.length == limit)
                return;
            if (rem) {
                if (prev == null)
                    head = b;
                else
                    prev.next = b;
            }
            b.vals.push(current.pop());
        } else if (current.length + 1 > best.length) {
            best = current.slice();
            best.push(b.vals[b.vals.length - 1]);
        }
        prev = b;
        b = b.next;
    }
    occupied[pos] = false;
}
+3

, , O (n!). O() .

, , , .

, , ( - ). , .

"" : (1,3), (3,1) → (1,3,1), (3,1,3)

() , algo: : 4 : 4 : 1,2,3,3

* , 3 ( , , , ( ) : (1,1), (1,2), (1,3), (2,1), (2,2), (2,3), (3,1), (3,2), (3,3)

: (1,2,1), (2,1,2), (2,3,2), (3,2,3), (1,2,3), (2,3,3), (3,2,1), (3,3,2)

: (1,2,3, x), (3,2,1, x)


++ ( placeCoins - algo): , , , cpp, , , , , , , . , , ( / , , , )

#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
#include <map>

using namespace std;

//min clock size 3
vector<vector<int>> placeCoins(int _clockSize, vector<int> _coins)
{
    int totalCheckedCombos = 0;
    vector<vector<int>> coinGroups;
    vector<int> coinSet = _coins;
    sort(coinSet.begin(), coinSet.end());
    coinSet.erase(unique(coinSet.begin(), coinSet.end()), coinSet.end());

    map<int, int> coinCounts;
    for (int i = 0; i < coinSet.size(); i++)
    {
        coinCounts[coinSet.at(i)] = count(_coins.begin(), _coins.end(), coinSet.at(i));
    }

    cout << "pairs" << endl;
    //generate fair pairs of coins
    for (int i = 0; i < coinSet.size(); i++)
    {
        for (int ii = 0; ii < coinSet.size(); ii++)
        {
            if ((coinSet.at(i) + coinSet.at(ii)) % _clockSize != 0)
            {
                if (i == ii)
                {
                    if (coinCounts[coinSet.at(i)] > 1)
                    {
                        coinGroups.push_back({ coinSet.at(i),coinSet.at(ii) });
                    }
                }
                else
                {
                    coinGroups.push_back({ coinSet.at(i),coinSet.at(ii) });
                }
            }
        }
    }

    cout << "combine" << endl;
    //iteratively combine groups of coins
    for (int comboSize = 3; comboSize < _clockSize; comboSize++)
    {
        totalCheckedCombos += coinGroups.size();
        vector<vector<int>> nextSizeCombos;
        for (int i = 0; i < coinGroups.size(); i++)
        {
            for (int ii = 0; ii < coinGroups.size(); ii++)
            {
                //check combo to match  
                bool match = true;
                for (int a = 0; a < comboSize - 2; a++)
                {
                    if (coinGroups.at(i).at(a+1) != coinGroups.at(ii).at(a))
                    {
                        match = false;
                        break;
                    }
                }

                //check sum
                if (match)
                {
                    vector<int> tempCombo = coinGroups.at(i);
                    int newVal = coinGroups.at(ii).at(coinGroups.at(ii).size()-1);
                    tempCombo.push_back(newVal);
                    if (coinCounts[newVal] >= count(tempCombo.begin(), tempCombo.end(), newVal))
                    {
                        if (accumulate(tempCombo.begin(), tempCombo.end(), 0) % _clockSize != 0)
                        {
                            nextSizeCombos.push_back(tempCombo);
                        }
                    }
                }
            }
        }

        if (nextSizeCombos.size() == 0)
        {
            //finished, no next size combos found
            break;
        }
        else
        {
            cout << nextSizeCombos.size() << endl;
            coinGroups = nextSizeCombos;
        }
    }
    cout << "total combos checked: " << totalCheckedCombos << endl;
    return coinGroups;
}


int main(int argc, char** argv) {
    int clockSize;
    int coinCount;
    vector<int> coins = {};



    cout << "enter clock size: " << endl;
    cin >> clockSize;

    cout << "count number: " << endl;
    cin >> coinCount;

    for (int i = 0; i < coinCount; i++)
    {
        int tempCoin;
        cin >> tempCoin;
        coins.push_back(tempCoin);
    }

    cout << "press enter to compute combos: " << endl;
    cin.get();
    cin.get();

    vector<vector<int>> resultOrders = placeCoins(clockSize, coins);
    for (int i = 0; i < resultOrders.size(); i++)
    {
        cout << resultOrders.at(0).size()+1 << endl;
        for (int ii = 0; ii < resultOrders.at(i).size(); ii++)
        {
            cout << resultOrders.at(i).at(ii) << " , ";
        }
        cout <<"x"<< endl;
    }

    cin.get();
    cin.get();
}

ps: although I was debugging this in a stable state, it still could definitely use fine tuning and optimization, but it is a variable for different languages, so I just got an algorithm for working and called it good enough. If you see something clearly wrong or bad, feel free to comment and I will fix it (or edit it if you want).

+1
source

Instead of a greedy approach, try maximizing your coin against knocking out a coin.

def valueOfClock(capacity, coins, n, hour):
    if (n == 0 or capacity == 0 or hour > 12):
        return 0

    # Choose next coin if value is greater than the capacity
    if (coins[n-1] > capacity):
        valueOfClock(capacity, coins, n-1, hours)

    # Choose max value of either choosing the next coin or
    # choosing the current coin
    return max(valueOfClock(capacity, coins, n-1, hours),
               valueOfClock(capacity-coins[n-1], coins, n-1, hours + coins[n-1]))
-1
source

Source: https://habr.com/ru/post/1676567/


All Articles